<commit_before>// Copyright 2013 The go-github 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 github

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestRepositoriesService_ListCollaborators(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"page": "2"})
		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListCollaboratorsOptions{
		ListOptions: ListOptions{Page: 2},
	}
	ctx := context.Background()
	users, _, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListCollaborators returned error: %v", err)
	}

	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
	if !reflect.DeepEqual(users, want) {
		t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want)
	}
}

func TestRepositoriesService_ListCollaborators_withAffiliation(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"affiliation": "all", "page": "2"})
		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListCollaboratorsOptions{
		ListOptions: ListOptions{Page: 2},
		Affiliation: "all",
	}
	ctx := context.Background()
	users, _, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListCollaborators returned error: %v", err)
	}

	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
	if !reflect.DeepEqual(users, want) {
		t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want)
	}
}

func TestRepositoriesService_ListCollaborators_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.ListCollaborators(ctx, "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_IsCollaborator_True(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		w.WriteHeader(http.StatusNoContent)
	})

	ctx := context.Background()
	isCollab, _, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.IsCollaborator returned error: %v", err)
	}

	if !isCollab {
		t.Errorf("Repositories.IsCollaborator returned false, want true")
	}
}

func TestRepositoriesService_IsCollaborator_False(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		w.WriteHeader(http.StatusNotFound)
	})

	ctx := context.Background()
	isCollab, _, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.IsCollaborator returned error: %v", err)
	}

	if isCollab {
		t.Errorf("Repositories.IsCollaborator returned true, want false")
	}
}

func TestRepositoriesService_IsCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.IsCollaborator(ctx, "%", "%", "%")
	testURLParseError(t, err)
}

func TestRepositoryService_GetPermissionLevel(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u/permission", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprintf(w, `{"permission":"admin","user":{"login":"u"}}`)
	})

	ctx := context.Background()
	rpl, _, err := client.Repositories.GetPermissionLevel(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.GetPermissionLevel returned error: %v", err)
	}

	want := &RepositoryPermissionLevel{
		Permission: String("admin"),
		User: &User{
			Login: String("u"),
		},
	}

	if !reflect.DeepEqual(rpl, want) {
		t.Errorf("Repositories.GetPermissionLevel returned %+v, want %+v", rpl, want)
	}
}

func TestRepositoriesService_AddCollaborator(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	opt := &RepositoryAddCollaboratorOptions{Permission: "admin"}
	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		v := new(RepositoryAddCollaboratorOptions)
		json.NewDecoder(r.Body).Decode(v)
		testMethod(t, r, "PUT")
		if !reflect.DeepEqual(v, opt) {
			t.Errorf("Request body = %+v, want %+v", v, opt)
		}
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"permissions": "write","url": "https://api.github.com/user/repository_invitations/1296269","html_url": "https://github.com/octocat/Hello-World/invitations","id":1,"permissions":"write","repository":{"url":"s","name":"r","id":1},"invitee":{"login":"u"},"inviter":{"login":"o"}}`))
	})
	ctx := context.Background()
	collaboratorInvitation, _, err := client.Repositories.AddCollaborator(ctx, "o", "r", "u", opt)
	if err != nil {
		t.Errorf("Repositories.AddCollaborator returned error: %v", err)
	}
	want := &CollaboratorInvitation{
		ID: Int64(1),
		Repo: &Repository{
			ID:   Int64(1),
			URL:  String("s"),
			Name: String("r"),
		},
		Invitee: &User{
			Login: String("u"),
		},
		Inviter: &User{
			Login: String("o"),
		},
		Permissions: String("write"),
		URL:         String("https://api.github.com/user/repository_invitations/1296269"),
		HTMLURL:     String("https://github.com/octocat/Hello-World/invitations"),
	}

	if !reflect.DeepEqual(collaboratorInvitation, want) {
		t.Errorf("AddCollaborator returned %+v, want %+v", collaboratorInvitation, want)
	}
}

func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.AddCollaborator(ctx, "%", "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_RemoveCollaborator(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	ctx := context.Background()
	_, err := client.Repositories.RemoveCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.RemoveCollaborator returned error: %v", err)
	}
}

func TestRepositoriesService_RemoveCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.RemoveCollaborator(ctx, "%", "%", "%")
	testURLParseError(t, err)
}
<commit_msg>Improve repos_collaborators.go coverage (#1744)<commit_after>// Copyright 2013 The go-github 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 github

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"testing"
)

func TestRepositoriesService_ListCollaborators(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"page": "2"})
		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListCollaboratorsOptions{
		ListOptions: ListOptions{Page: 2},
	}
	ctx := context.Background()
	users, _, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListCollaborators returned error: %v", err)
	}

	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
	if !reflect.DeepEqual(users, want) {
		t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want)
	}

	const methodName = "ListCollaborators"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListCollaborators(ctx, "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListCollaborators_withAffiliation(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		testFormValues(t, r, values{"affiliation": "all", "page": "2"})
		fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
	})

	opt := &ListCollaboratorsOptions{
		ListOptions: ListOptions{Page: 2},
		Affiliation: "all",
	}
	ctx := context.Background()
	users, _, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
	if err != nil {
		t.Errorf("Repositories.ListCollaborators returned error: %v", err)
	}

	want := []*User{{ID: Int64(1)}, {ID: Int64(2)}}
	if !reflect.DeepEqual(users, want) {
		t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want)
	}

	const methodName = "ListCollaborators"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.ListCollaborators(ctx, "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.ListCollaborators(ctx, "o", "r", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_ListCollaborators_invalidOwner(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.ListCollaborators(ctx, "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_IsCollaborator_True(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		w.WriteHeader(http.StatusNoContent)
	})

	ctx := context.Background()
	isCollab, _, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.IsCollaborator returned error: %v", err)
	}

	if !isCollab {
		t.Errorf("Repositories.IsCollaborator returned false, want true")
	}

	const methodName = "IsCollaborator"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.IsCollaborator(ctx, "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
		if got {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want false", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_IsCollaborator_False(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		w.WriteHeader(http.StatusNotFound)
	})

	ctx := context.Background()
	isCollab, _, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.IsCollaborator returned error: %v", err)
	}

	if isCollab {
		t.Errorf("Repositories.IsCollaborator returned true, want false")
	}

	const methodName = "IsCollaborator"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.IsCollaborator(ctx, "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.IsCollaborator(ctx, "o", "r", "u")
		if got {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want false", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_IsCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.IsCollaborator(ctx, "%", "%", "%")
	testURLParseError(t, err)
}

func TestRepositoryService_GetPermissionLevel(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u/permission", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "GET")
		fmt.Fprintf(w, `{"permission":"admin","user":{"login":"u"}}`)
	})

	ctx := context.Background()
	rpl, _, err := client.Repositories.GetPermissionLevel(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.GetPermissionLevel returned error: %v", err)
	}

	want := &RepositoryPermissionLevel{
		Permission: String("admin"),
		User: &User{
			Login: String("u"),
		},
	}

	if !reflect.DeepEqual(rpl, want) {
		t.Errorf("Repositories.GetPermissionLevel returned %+v, want %+v", rpl, want)
	}

	const methodName = "GetPermissionLevel"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.GetPermissionLevel(ctx, "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.GetPermissionLevel(ctx, "o", "r", "u")
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_AddCollaborator(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	opt := &RepositoryAddCollaboratorOptions{Permission: "admin"}
	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		v := new(RepositoryAddCollaboratorOptions)
		json.NewDecoder(r.Body).Decode(v)
		testMethod(t, r, "PUT")
		if !reflect.DeepEqual(v, opt) {
			t.Errorf("Request body = %+v, want %+v", v, opt)
		}
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"permissions": "write","url": "https://api.github.com/user/repository_invitations/1296269","html_url": "https://github.com/octocat/Hello-World/invitations","id":1,"permissions":"write","repository":{"url":"s","name":"r","id":1},"invitee":{"login":"u"},"inviter":{"login":"o"}}`))
	})
	ctx := context.Background()
	collaboratorInvitation, _, err := client.Repositories.AddCollaborator(ctx, "o", "r", "u", opt)
	if err != nil {
		t.Errorf("Repositories.AddCollaborator returned error: %v", err)
	}
	want := &CollaboratorInvitation{
		ID: Int64(1),
		Repo: &Repository{
			ID:   Int64(1),
			URL:  String("s"),
			Name: String("r"),
		},
		Invitee: &User{
			Login: String("u"),
		},
		Inviter: &User{
			Login: String("o"),
		},
		Permissions: String("write"),
		URL:         String("https://api.github.com/user/repository_invitations/1296269"),
		HTMLURL:     String("https://github.com/octocat/Hello-World/invitations"),
	}

	if !reflect.DeepEqual(collaboratorInvitation, want) {
		t.Errorf("AddCollaborator returned %+v, want %+v", collaboratorInvitation, want)
	}

	const methodName = "AddCollaborator"
	testBadOptions(t, methodName, func() (err error) {
		_, _, err = client.Repositories.AddCollaborator(ctx, "\n", "\n", "\n", opt)
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		got, resp, err := client.Repositories.AddCollaborator(ctx, "o", "r", "u", opt)
		if got != nil {
			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
		}
		return resp, err
	})
}

func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, _, err := client.Repositories.AddCollaborator(ctx, "%", "%", "%", nil)
	testURLParseError(t, err)
}

func TestRepositoriesService_RemoveCollaborator(t *testing.T) {
	client, mux, _, teardown := setup()
	defer teardown()

	mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, "DELETE")
		w.WriteHeader(http.StatusNoContent)
	})

	ctx := context.Background()
	_, err := client.Repositories.RemoveCollaborator(ctx, "o", "r", "u")
	if err != nil {
		t.Errorf("Repositories.RemoveCollaborator returned error: %v", err)
	}

	const methodName = "RemoveCollaborator"
	testBadOptions(t, methodName, func() (err error) {
		_, err = client.Repositories.RemoveCollaborator(ctx, "\n", "\n", "\n")
		return err
	})

	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
		return client.Repositories.RemoveCollaborator(ctx, "o", "r", "u")
	})
}

func TestRepositoriesService_RemoveCollaborator_invalidUser(t *testing.T) {
	client, _, _, teardown := setup()
	defer teardown()

	ctx := context.Background()
	_, err := client.Repositories.RemoveCollaborator(ctx, "%", "%", "%")
	testURLParseError(t, err)
}
<|endoftext|>


<commit_before>// Package markdown is middleware to render markdown files as HTML
// on-the-fly.
package markdown

import (
	"io/ioutil"
	"net/http"
	"path"
	"strings"

	"github.com/mholt/caddy/middleware"
	"github.com/russross/blackfriday"
)

// New creates a new instance of Markdown middleware that
// renders markdown to HTML on-the-fly.
func New(c middleware.Controller) (middleware.Middleware, error) {
	md, err := parse(c)
	if err != nil {
		return nil, err
	}

	md.Root = c.Root()
	md.Renderer = blackfriday.HtmlRenderer(0, "", "")

	return func(next http.HandlerFunc) http.HandlerFunc {
		md.Next = next
		return md.ServeHTTP
	}, nil
}

// Markdown stores the configuration necessary to serve the Markdown middleware.
type Markdown struct {
	// Server root
	Root string

	// Next HTTP handler in the chain
	Next http.HandlerFunc

	// Markdown renderer
	Renderer blackfriday.Renderer

	// Base path to match
	PathScope string

	// List of extensions to consider as markdown files
	Extensions []string

	// List of style sheets to load for each markdown file
	Styles []string

	// List of JavaScript files to load for each markdown file
	Scripts []string
}

// ServeHTTP implements the http.Handler interface.
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if middleware.Path(r.URL.Path).Matches(m.PathScope) {
		for _, ext := range m.Extensions {
			if strings.HasSuffix(r.URL.Path, ext) {
				fpath := m.Root + r.URL.Path

				body, err := ioutil.ReadFile(fpath)
				if err != nil {
					panic(err) // TODO
				}

				content := blackfriday.Markdown(body, m.Renderer, 0)

				var scripts, styles string
				for _, style := range m.Styles {
					styles += strings.Replace(cssTemplate, "{{url}}", style, 1) + "\r\n"
				}
				for _, script := range m.Scripts {
					scripts += strings.Replace(jsTemplate, "{{url}}", script, 1) + "\r\n"
				}

				html := htmlTemplate
				html = strings.Replace(html, "{{title}}", path.Base(fpath), 1)
				html = strings.Replace(html, "{{css}}", styles, 1)
				html = strings.Replace(html, "{{js}}", scripts, 1)
				html = strings.Replace(html, "{{body}}", string(content), 1)

				w.Write([]byte(html))
				return
			}
		}
	}

	m.Next(w, r)
}

// parse fills up a new instance of Markdown middleware.
func parse(c middleware.Controller) (Markdown, error) {
	var md Markdown

	for c.Next() {
		// Get the path scope
		if !c.NextArg() {
			return md, c.ArgErr()
		}
		md.PathScope = c.Val()

		// Load any other configuration parameters
		for c.NextBlock() {
			switch c.Val() {
			case "ext":
				exts := c.RemainingArgs()
				if len(exts) == 0 {
					return md, c.ArgErr()
				}
				md.Extensions = append(md.Extensions, exts...)
			case "css":
				if !c.NextArg() {
					return md, c.ArgErr()
				}
				md.Styles = append(md.Styles, c.Val())
			case "js":
				if !c.NextArg() {
					return md, c.ArgErr()
				}
				md.Scripts = append(md.Scripts, c.Val())
			default:
				return md, c.Err("Expected valid markdown configuration property")
			}
		}

	}

	return md, nil
}

const (
	htmlTemplate = `<!DOCTYPE html>
<html>
	<head>
		<title>{{title}}</title>
		<meta charset="utf-8">
		{{css}}
		{{js}}
	</head>
	<body>
		{{body}}
	</body>
</html>`
	cssTemplate = `<link rel="stylesheet" href="{{url}}">`
	jsTemplate  = `<script src="{{url}}"></script>`
)
<commit_msg>Markdown handles titles a little better<commit_after>// Package markdown is middleware to render markdown files as HTML
// on-the-fly.
package markdown

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"path"
	"strings"

	"github.com/mholt/caddy/middleware"
	"github.com/russross/blackfriday"
)

// New creates a new instance of Markdown middleware that
// renders markdown to HTML on-the-fly.
func New(c middleware.Controller) (middleware.Middleware, error) {
	md, err := parse(c)
	if err != nil {
		return nil, err
	}

	md.Root = c.Root()
	md.Renderer = blackfriday.HtmlRenderer(0, "", "")

	return func(next http.HandlerFunc) http.HandlerFunc {
		md.Next = next
		return md.ServeHTTP
	}, nil
}

// Markdown stores the configuration necessary to serve the Markdown middleware.
type Markdown struct {
	// Server root
	Root string

	// Next HTTP handler in the chain
	Next http.HandlerFunc

	// Markdown renderer
	Renderer blackfriday.Renderer

	// Base path to match
	PathScope string

	// List of extensions to consider as markdown files
	Extensions []string

	// List of style sheets to load for each markdown file
	Styles []string

	// List of JavaScript files to load for each markdown file
	Scripts []string
}

// ServeHTTP implements the http.Handler interface.
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if middleware.Path(r.URL.Path).Matches(m.PathScope) {
		for _, ext := range m.Extensions {
			if strings.HasSuffix(r.URL.Path, ext) {
				fpath := m.Root + r.URL.Path

				body, err := ioutil.ReadFile(fpath)
				if err != nil {
					panic(err) // TODO
				}

				content := blackfriday.Markdown(body, m.Renderer, 0)

				var scripts, styles string
				for _, style := range m.Styles {
					styles += strings.Replace(cssTemplate, "{{url}}", style, 1) + "\r\n"
				}
				for _, script := range m.Scripts {
					scripts += strings.Replace(jsTemplate, "{{url}}", script, 1) + "\r\n"
				}

				// Title is first line (length-limited), otherwise filename
				title := path.Base(fpath)
				newline := bytes.Index(body, []byte("\n"))
				if newline > -1 {
					firstline := body[:newline]
					newTitle := strings.TrimSpace(string(firstline))
					if len(newTitle) > 1 {
						if len(newTitle) > 128 {
							title = newTitle[:128]
						} else {
							title = newTitle
						}
					}
				}

				html := htmlTemplate
				html = strings.Replace(html, "{{title}}", title, 1)
				html = strings.Replace(html, "{{css}}", styles, 1)
				html = strings.Replace(html, "{{js}}", scripts, 1)
				html = strings.Replace(html, "{{body}}", string(content), 1)

				w.Write([]byte(html))

				return
			}
		}
	}

	// Didn't qualify to serve as markdown; pass-thru
	m.Next(w, r)
}

// parse fills up a new instance of Markdown middleware.
func parse(c middleware.Controller) (Markdown, error) {
	var md Markdown

	for c.Next() {
		// Get the path scope
		if !c.NextArg() {
			return md, c.ArgErr()
		}
		md.PathScope = c.Val()

		// Load any other configuration parameters
		for c.NextBlock() {
			switch c.Val() {
			case "ext":
				exts := c.RemainingArgs()
				if len(exts) == 0 {
					return md, c.ArgErr()
				}
				md.Extensions = append(md.Extensions, exts...)
			case "css":
				if !c.NextArg() {
					return md, c.ArgErr()
				}
				md.Styles = append(md.Styles, c.Val())
			case "js":
				if !c.NextArg() {
					return md, c.ArgErr()
				}
				md.Scripts = append(md.Scripts, c.Val())
			default:
				return md, c.Err("Expected valid markdown configuration property")
			}
		}

	}

	return md, nil
}

const (
	htmlTemplate = `<!DOCTYPE html>
<html>
	<head>
		<title>{{title}}</title>
		<meta charset="utf-8">
		{{css}}
		{{js}}
	</head>
	<body>
		{{body}}
	</body>
</html>`
	cssTemplate = `<link rel="stylesheet" href="{{url}}">`
	jsTemplate  = `<script src="{{url}}"></script>`
)
<|endoftext|>


<commit_before>package test

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"syscall"
	"testing"
	"time"

	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"

	"github.com/coreos/etcd/tests"
	"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)

// remove the node and node rejoin with previous log
func TestRemoveNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	argGroup, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "syncInterval":1}`))
	if !assert.Equal(t, resp.StatusCode, 200) {
		t.FailNow()
	}

	rmReq, _ := http.NewRequest("DELETE", "http://127.0.0.1:7001/remove/node3", nil)

	client := &http.Client{}
	for i := 0; i < 2; i++ {
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			client.Do(rmReq)

			fmt.Println("send remove to node3 and wait for its exiting")
			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			etcds[2].Kill()
			etcds[2].Wait()

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, argGroup[2], procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #1 (%d != 4)", len(resp.Node.Nodes))
			}
		}

		// first kill the node, then remove it, then add it back
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			etcds[2].Kill()
			fmt.Println("kill node3 and wait for its exiting")
			etcds[2].Wait()

			client.Do(rmReq)

			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2]), procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #2 (%d != 4)", len(resp.Node.Nodes))
			}
		}
	}
}

func TestRemovePausedNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	_, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":1, "syncInterval":1}`))
	if !assert.Equal(t, r.StatusCode, 200) {
		t.FailNow()
	}
	time.Sleep(2 * time.Second)

	resp, err := c.Get("_etcd/machines", false, false)
	if err != nil {
		panic(err)
	}
	if len(resp.Node.Nodes) != 3 {
		t.Fatal("cannot remove peer")
	}

	for i := 0; i < clusterSize; i++ {
		// first pause the node, then remove it, then resume it
		idx := rand.Int() % clusterSize

		etcds[idx].Signal(syscall.SIGSTOP)
		fmt.Printf("pause node%d and let standby node take its place\n", idx+1)
		time.Sleep(4 * time.Second)

		resp, err := c.Get("_etcd/machines", false, false)
		if err != nil {
			panic(err)
		}
		if len(resp.Node.Nodes) != 3 {
			t.Fatal("cannot remove peer")
		}
		for i := 0; i < 3; i++ {
			if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
				t.Fatal("node should be removed")
			}
		}

		etcds[idx].Signal(syscall.SIGCONT)
		// let it change its state to candidate at least
		time.Sleep(time.Second)

		stop := make(chan bool)
		leaderChan := make(chan string, 1)
		all := make(chan bool, 1)

		go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
		<-all
		<-leaderChan
		stop <- true

		resp, err = c.Get("_etcd/machines", false, false)
		if err != nil {
			panic(err)
		}
		if len(resp.Node.Nodes) != 3 {
			t.Fatalf("add peer fails (%d != 3)", len(resp.Node.Nodes))
		}
		for i := 0; i < 3; i++ {
			if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
				t.Fatal("node should be removed")
			}
		}
	}
}
<commit_msg>fix(test/remove_node_test.go) fix a deadlock in the test<commit_after>package test

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"syscall"
	"testing"
	"time"

	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"

	"github.com/coreos/etcd/tests"
	"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)

// remove the node and node rejoin with previous log
func TestRemoveNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	argGroup, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "syncInterval":1}`))
	if !assert.Equal(t, resp.StatusCode, 200) {
		t.FailNow()
	}

	rmReq, _ := http.NewRequest("DELETE", "http://127.0.0.1:7001/remove/node3", nil)

	client := &http.Client{}
	for i := 0; i < 2; i++ {
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			client.Do(rmReq)

			fmt.Println("send remove to node3 and wait for its exiting")
			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			etcds[2].Kill()
			etcds[2].Wait()

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, argGroup[2], procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #1 (%d != 4)", len(resp.Node.Nodes))
			}
		}

		// first kill the node, then remove it, then add it back
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			etcds[2].Kill()
			fmt.Println("kill node3 and wait for its exiting")
			etcds[2].Wait()

			client.Do(rmReq)

			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2]), procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #2 (%d != 4)", len(resp.Node.Nodes))
			}
		}
	}
}

func TestRemovePausedNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	_, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":1, "syncInterval":1}`))
	if !assert.Equal(t, r.StatusCode, 200) {
		t.FailNow()
	}
	time.Sleep(2 * time.Second)

	resp, err := c.Get("_etcd/machines", false, false)
	if err != nil {
		panic(err)
	}
	if len(resp.Node.Nodes) != 3 {
		t.Fatal("cannot remove peer")
	}

	for i := 0; i < clusterSize; i++ {
		// first pause the node, then remove it, then resume it
		idx := rand.Int() % clusterSize

		etcds[idx].Signal(syscall.SIGSTOP)
		fmt.Printf("pause node%d and let standby node take its place\n", idx+1)

		time.Sleep(4 * time.Second)

		etcds[idx].Signal(syscall.SIGCONT)
		// let it change its state to candidate at least
		time.Sleep(time.Second)

		stop := make(chan bool)
		leaderChan := make(chan string, 1)
		all := make(chan bool, 1)

		go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
		<-all
		<-leaderChan
		stop <- true

		resp, err = c.Get("_etcd/machines", false, false)
		if err != nil {
			panic(err)
		}
		if len(resp.Node.Nodes) != 3 {
			t.Fatalf("add peer fails (%d != 3)", len(resp.Node.Nodes))
		}
		for i := 0; i < 3; i++ {
			if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
				t.Fatal("node should be removed")
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"github.com/gdevillele/frontparser"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// TestFrontmatterTitle tests if there's a title present in all
// published markdown frontmatters.
func TestFrontmatterTitle(t *testing.T) {
	filepath.Walk("/docs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		published, mdBytes, err := isPublishedMarkdown(path)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		if published == false {
			return nil
		}
		err = testFrontmatterTitle(mdBytes)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		return nil
	})
}

// testFrontmatterTitle tests if there's a title present in
// given markdown file bytes
func testFrontmatterTitle(mdBytes []byte) error {
	fm, _, err := frontparser.ParseFrontmatterAndContent(mdBytes)
	if err != nil {
		return err
	}
	if _, exists := fm["title"]; exists == false {
		return errors.New("can't find title in frontmatter")
	}
	return nil
}

// TestFrontMatterKeywords tests if keywords are present and correctly
// formatted in all published markdown frontmatters.
func TestFrontMatterKeywords(t *testing.T) {
	filepath.Walk("/docs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		published, mdBytes, err := isPublishedMarkdown(path)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		if published == false {
			return nil
		}
		err = testFrontMatterKeywords(mdBytes)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		return nil
	})
}

// testFrontMatterKeywords tests if if keywords are present and correctly
// formatted in given markdown file bytes
func testFrontMatterKeywords(mdBytes []byte) error {
	fm, _, err := frontparser.ParseFrontmatterAndContent(mdBytes)
	if err != nil {
		return err
	}

	keywords, exists := fm["keywords"]

	// it's ok to have a page without keywords
	if exists == false {
		return nil
	}

	if _, ok := keywords.(string); !ok {
		return errors.New("keywords should be a comma separated string")
	}

	return nil
}

//-----------------
// utils
//-----------------

// isPublishedMarkdown returns wether a file is a published markdown or not
// as a convenience it also returns the markdown bytes to avoid reading files twice
func isPublishedMarkdown(path string) (bool, []byte, error) {
	if strings.HasSuffix(path, ".md") {
		fileBytes, err := ioutil.ReadFile(path)
		if err != nil {
			return false, nil, err
		}
		if frontparser.HasFrontmatterHeader(fileBytes) {
			fm, _, err := frontparser.ParseFrontmatterAndContent(fileBytes)
			if err != nil {
				return false, nil, err
			}
			// skip markdowns that are not published
			if published, exists := fm["published"]; exists {
				if publishedBool, ok := published.(bool); ok {
					if publishedBool {
						// file is markdown, has frontmatter and is published
						return true, fileBytes, nil
					}
				}
			} else {
				// if "published" field is missing, it means published == true
				return true, fileBytes, nil
			}
		}
	}
	return false, nil, nil
}
<commit_msg>added test for absolute links to docs.docker.com<commit_after>package main

import (
	"errors"
	"github.com/gdevillele/frontparser"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

// TestFrontmatterTitle tests if there's a title present in all
// published markdown frontmatters.
func TestFrontmatterTitle(t *testing.T) {
	filepath.Walk("/docs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		published, mdBytes, err := isPublishedMarkdown(path)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		if published == false {
			return nil
		}
		err = testFrontmatterTitle(mdBytes)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		return nil
	})
}

// testFrontmatterTitle tests if there's a title present in
// given markdown file bytes
func testFrontmatterTitle(mdBytes []byte) error {
	fm, _, err := frontparser.ParseFrontmatterAndContent(mdBytes)
	if err != nil {
		return err
	}
	if _, exists := fm["title"]; exists == false {
		return errors.New("can't find title in frontmatter")
	}
	return nil
}

// TestFrontMatterKeywords tests if keywords are present and correctly
// formatted in all published markdown frontmatters.
func TestFrontMatterKeywords(t *testing.T) {
	filepath.Walk("/docs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		published, mdBytes, err := isPublishedMarkdown(path)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		if published == false {
			return nil
		}
		err = testFrontMatterKeywords(mdBytes)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		return nil
	})
}

// testFrontMatterKeywords tests if if keywords are present and correctly
// formatted in given markdown file bytes
func testFrontMatterKeywords(mdBytes []byte) error {
	fm, _, err := frontparser.ParseFrontmatterAndContent(mdBytes)
	if err != nil {
		return err
	}

	keywords, exists := fm["keywords"]

	// it's ok to have a page without keywords
	if exists == false {
		return nil
	}

	if _, ok := keywords.(string); !ok {
		return errors.New("keywords should be a comma separated string")
	}

	return nil
}

// TestURLs tests if we're not using absolute paths for URLs
// when pointing to local pages.
func TestURLs(t *testing.T) {
	filepath.Walk("/docs", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		published, mdBytes, err := isPublishedMarkdown(path)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		if published == false {
			return nil
		}
		err = testURLs(mdBytes)
		if err != nil {
			t.Error(err.Error(), "-", path)
		}
		return nil
	})
}

// testURLs tests if we're not using absolute paths for URLs
// when pointing to local pages.
func testURLs(mdBytes []byte) error {
	_, md, err := frontparser.ParseFrontmatterAndContent(mdBytes)
	if err != nil {
		return err
	}

	regularExpression, err := regexp.Compile(`\[[^\]]+\]\(([^\)]+)\)`)
	if err != nil {
		return err
	}

	submatches := regularExpression.FindAllStringSubmatch(string(md), -1)

	for _, submatch := range submatches {
		if strings.Contains(submatch[1], "docs.docker.com") {
			return errors.New("found absolute link (" + strings.TrimSpace(submatch[1]) + ")")
		}
	}

	return nil
}

//-----------------
// utils
//-----------------

// isPublishedMarkdown returns wether a file is a published markdown or not
// as a convenience it also returns the markdown bytes to avoid reading files twice
func isPublishedMarkdown(path string) (bool, []byte, error) {
	if strings.HasSuffix(path, ".md") {
		fileBytes, err := ioutil.ReadFile(path)
		if err != nil {
			return false, nil, err
		}
		if frontparser.HasFrontmatterHeader(fileBytes) {
			fm, _, err := frontparser.ParseFrontmatterAndContent(fileBytes)
			if err != nil {
				return false, nil, err
			}
			// skip markdowns that are not published
			if published, exists := fm["published"]; exists {
				if publishedBool, ok := published.(bool); ok {
					if publishedBool {
						// file is markdown, has frontmatter and is published
						return true, fileBytes, nil
					}
				}
			} else {
				// if "published" field is missing, it means published == true
				return true, fileBytes, nil
			}
		}
	}
	return false, nil, nil
}
<|endoftext|>


<commit_before>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gregex_test

import (
	"bytes"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/text/gregex"
	"strings"
)

func ExampleIsMatch() {
	patternStr := `[1-9]\d+`
	g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello gf!")))
	g.Dump(gregex.IsMatch(patternStr, nil))
	g.Dump(gregex.IsMatch(patternStr, []byte("hello gf!")))

	// Output
	// true
	// false
	// false
}

func ExampleIsMatchString() {
	patternStr := `[1-9]\d+`
	g.Dump(gregex.IsMatchString(patternStr, "hello 2022! hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, "hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, ""))

	// Output
	// true
	// false
	// false
}

func ExampleMatch() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.Match(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.MatchString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchAll() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAll(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleMatchAllString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAllString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleQuote() {
	result := gregex.Quote(`[1-9]\d+`)
	g.Dump(result)

	// Output
	// "\[1-9\]\\d\+"
}

func ExampleReplace() {
	var (
		patternStr  = `[1-9]\d+`
		str         = "hello gf 2020!"
		repStr      = "2021"
		result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str))
	)
	g.Dump(err)
	g.Dump(result)

	// Output
	// <nil>
	// "hello gf 2021!"
}

func ExampleReplaceFunc() {
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpattern that use the matching function
	result, err := gregex.ReplaceFuncMatch(`(\d+)~(\d+)`, []byte("hello gf 2018~2020!"), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("~"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//     "2018~2020",
	//     "2018",
	//     "2020",
	// ]
	// "hello gf 2018~2021!"
	// <nil>
}

func ExampleReplaceFuncMatch() {
	var (
		patternStr = `(\d+)~(\d+)`
		str        = "hello gf 2018~2020!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("-"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["2018~2020","2018","2020"]
	// "hello gf 2018-2021!"
	// <nil>
}

func ExampleReplaceString() {
	patternStr := `[1-9]\d+`
	str := "hello gf 2020!"
	replaceStr := "2021"
	result, err := gregex.ReplaceString(patternStr, replaceStr, str)

	g.Dump(result)
	g.Dump(err)

	// Output
	// "hello gf 2021!"
	// <nil>
}

func ExampleReplaceStringFunc() {
	replaceStrMap := map[string]string{
		"2020": "2021",
	}
	// When the regular statement can match multiple results
	// func can be used to further control the value that needs to be modified
	result, err := gregex.ReplaceStringFunc(`[1-9]\d+`, `hello gf 2018~2020!`, func(b string) string {
		g.Dump(b)
		if replaceStr, ok := replaceStrMap[b]; ok {
			return replaceStr
		}
		return b
	})
	g.Dump(result)
	g.Dump(err)

	result, err = gregex.ReplaceStringFunc(`[a-z]*`, "gf@goframe.org", strings.ToUpper)
	g.Dump(result)
	g.Dump(err)

	// Output
	// "2018"
	// "2020"
	// "hello gf 2018~2021!"
	// <nil>
	// "GF@GOFRAME.ORG"
	// <nil>
}

func ExampleReplaceStringFuncMatch() {
	var (
		patternStr = `([A-Z])\w+`
		str        = "hello Golang 2018~2021!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string {
		g.Dump(match)
		match[0] = "Gf"
		return match[0]
	})
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["Golang","G"]
	// "hello Gf 2018~2021!"
	// <nil>
}

func ExampleSplit() {
	patternStr := `[1-9]\d+`
	str := "hello2020gf"
	result := gregex.Split(patternStr, str)
	g.Dump(result)

	// Output
	// ["hello","gf"]
}

func ExampleValidate() {
	// Valid match statement
	g.Dump(gregex.Validate(`[1-9]\d+`))
	// Mismatched statement
	g.Dump(gregex.Validate(`[a-9]\d+`))

	// Output
	// <nil>
	// {
	//	"Code": "invalid character class range",
	//	"Expr": "a-9"
	// }
}
<commit_msg>:memo: add example<commit_after>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gregex_test

import (
	"bytes"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/text/gregex"
	"strings"
)

func ExampleIsMatch() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello gf!")))
	g.Dump(gregex.IsMatch(patternStr, nil))
	g.Dump(gregex.IsMatch(patternStr, []byte("hello gf!")))

	// Output
	// true
	// false
	// false
}

func ExampleIsMatchString() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatchString(patternStr, "hello 2022! hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, "hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, ""))

	// Output
	// true
	// false
	// false
}

func ExampleMatch() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.Match(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.MatchString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchAll() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAll(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleMatchAllString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAllString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleQuote() {
	result := gregex.Quote(`[1-9]\d+`)
	g.Dump(result)

	// Output
	// "\[1-9\]\\d\+"
}

func ExampleReplace() {
	var (
		patternStr  = `\d+`
		str         = "hello gf 2020!"
		repStr      = "2021"
		result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str))
	)
	g.Dump(err)
	g.Dump(result)

	// Output
	// <nil>
	// "hello gf 2021!"
}

func ExampleReplaceFunc() {
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpattern that use the matching function
	result, err := gregex.ReplaceFuncMatch(`(\d+)~(\d+)`, []byte("hello gf 2018~2020!"), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("~"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//     "2018~2020",
	//     "2018",
	//     "2020",
	// ]
	// "hello gf 2018~2021!"
	// <nil>
}

func ExampleReplaceFuncMatch() {
	var (
		patternStr = `(\d+)~(\d+)`
		str        = "hello gf 2018~2020!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("-"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["2018~2020","2018","2020"]
	// "hello gf 2018-2021!"
	// <nil>
}

func ExampleReplaceString() {
	patternStr := `\d+`
	str := "hello gf 2020!"
	replaceStr := "2021"
	result, err := gregex.ReplaceString(patternStr, replaceStr, str)

	g.Dump(result)
	g.Dump(err)

	// Output
	// "hello gf 2021!"
	// <nil>
}

func ExampleReplaceStringFunc() {
	replaceStrMap := map[string]string{
		"2020": "2021",
	}
	// When the regular statement can match multiple results
	// func can be used to further control the value that needs to be modified
	result, err := gregex.ReplaceStringFunc(`\d+`, `hello gf 2018~2020!`, func(b string) string {
		g.Dump(b)
		if replaceStr, ok := replaceStrMap[b]; ok {
			return replaceStr
		}
		return b
	})
	g.Dump(result)
	g.Dump(err)

	result, err = gregex.ReplaceStringFunc(`[a-z]*`, "gf@goframe.org", strings.ToUpper)
	g.Dump(result)
	g.Dump(err)

	// Output
	// "2018"
	// "2020"
	// "hello gf 2018~2021!"
	// <nil>
	// "GF@GOFRAME.ORG"
	// <nil>
}

func ExampleReplaceStringFuncMatch() {
	var (
		patternStr = `([A-Z])\w+`
		str        = "hello Golang 2018~2021!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string {
		g.Dump(match)
		match[0] = "Gf"
		return match[0]
	})
	g.Dump(result)
	g.Dump(err)

	// Output
	// ["Golang","G"]
	// "hello Gf 2018~2021!"
	// <nil>
}

func ExampleSplit() {
	patternStr := `\d+`
	str := "hello2020gf"
	result := gregex.Split(patternStr, str)
	g.Dump(result)

	// Output
	// ["hello","gf"]
}

func ExampleValidate() {
	// Valid match statement
	g.Dump(gregex.Validate(`\d+`))
	// Mismatched statement
	g.Dump(gregex.Validate(`[a-9]\d+`))

	// Output
	// <nil>
	// {
	//	"Code": "invalid character class range",
	//	"Expr": "a-9"
	// }
}
<|endoftext|>


<commit_before>package tracker

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/jackpal/Taipei-Torrent/torrent"
	"io"
	"io/ioutil"
	"log"
	"math"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"
)

func TestScrapeURL(t *testing.T) {
	tests := []struct{ announce, scrape string }{
		{"", ""},
		{"foo", ""},
		{"x/announce", "x/scrape"},
		{"x/announce?ad#3", "x/scrape?ad#3"},
		{"announce/x", ""},
	}
	for _, test := range tests {
		scrape := ScrapePattern(test.announce)
		if scrape != test.scrape {
			t.Errorf("ScrapeURL(%#v) = %#v. Expected %#v", test.announce, scrape, test.scrape)
		}
	}
}

func TestSwarm1(t *testing.T) {
	testSwarm(t, 1)
}

func TestSwarm10(t *testing.T) {
	testSwarm(t, 10)
}

/* Larger sizes don't work correctly.

func TestSwarm20(t *testing.T) {
	testSwarm(t, 20)
}

func TestSwarm50(t *testing.T) {
	testSwarm(t, 50)
}

func TestSwarm100(t *testing.T) {
	testSwarm(t, 100)
}

*/

func testSwarm(t *testing.T, leechCount int) {
	err := runSwarm(leechCount)
	if err != nil {
		t.Fatal("Error running testSwarm", err)
	}
}

type prog struct {
	name    string
	dirName string
	cmd     *exec.Cmd
}

func (p *prog) start(doneCh chan *prog) (err error) {
	log.Println("starting", p.name)
	out := logWriter(p.name)
	p.cmd.Stdout = &out
	p.cmd.Stderr = &out
	err = p.cmd.Start()
	if err != nil {
		return
	}
	go func() {
		p.cmd.Wait()
		doneCh <- p
	}()
	return
}

func (p *prog) kill() (err error) {
	err = p.cmd.Process.Kill()
	return
}

func NewProg(instanceName string, dir string, name string, arg ...string) (p *prog) {
	cmd := exec.Command(name, arg...)
	return &prog{name: name, dirName: dir, cmd: cmd}
}

func runSwarm(leechCount int) (err error) {
	var rootDir string
	rootDir, err = ioutil.TempDir("", "swarm")
	if err != nil {
		return
	}
	log.Printf("Temporary directory: %s", rootDir)
	seedDir := path.Join(rootDir, "seed")
	err = os.Mkdir(seedDir, 0700)
	if err != nil {
		return
	}
	seedData := path.Join(seedDir, "data")
	err = createDataFile(seedData, 1024*1024)
	if err != nil {
		return
	}
	torrentFile := path.Join(rootDir, "testSwarm.torrent")
	err = createTorrentFile(torrentFile, seedData, "127.0.0.1:8080/announce")
	if err != nil {
		return
	}

	doneCh := make(chan *prog, 1)

	tracker := newTracker("tracker", ":8080", rootDir, torrentFile)
	err = tracker.start(doneCh)
	if err != nil {
		return
	}
	defer tracker.kill()
	time.Sleep(100 * time.Microsecond)

	var seed, leech *prog
	seed = newTorrentClient("seed", 7000, torrentFile, seedDir, math.Inf(0))
	err = seed.start(doneCh)
	if err != nil {
		return
	}
	defer seed.kill()
	time.Sleep(50 * time.Microsecond)

	for l := 0; l < leechCount; l++ {
		leechDir := path.Join(rootDir, fmt.Sprintf("leech %d", l))
		err = os.Mkdir(leechDir, 0700)
		if err != nil {
			return
		}
		leech = newTorrentClient(fmt.Sprintf("leech %d", l), 7001+l, torrentFile, leechDir, 0)
		err = leech.start(doneCh)
		if err != nil {
			return
		}
		defer leech.kill()
	}

	timeout := make(chan bool, 1)
	go func() {
		// It takes about 3.5 seconds to complete the test on my computer.
		time.Sleep(50 * time.Second)
		timeout <- true
	}()

	for doneCount := 0; doneCount < leechCount; doneCount++ {
		select {
		case <-timeout:
			err = fmt.Errorf("Timout exceeded")
		case donePeer := <-doneCh:
			if donePeer == tracker || donePeer == seed {
				err = fmt.Errorf("%v finished before all leeches. Should not have.", donePeer)
			}
			err = compareData(seedData, donePeer.dirName)
		}
		if err != nil {
			return
		}
		log.Printf("Done: %d of %d", (doneCount + 1), leechCount)
	}
	if err != nil {
		return
	}
	// All is good. Clean up
	os.RemoveAll(rootDir)

	return
}

func newTracker(name string, addr string, fileDir string, torrentFile string) (p *prog) {
	return NewProg(name, fileDir, "Taipei-Torrent",
		fmt.Sprintf("-createTracker=%v", addr),
		fmt.Sprintf("-fileDir=%v", fileDir),
		torrentFile)
}

func newTorrentClient(name string, port int, torrentFile string, fileDir string, ratio float64) (p *prog) {
	return NewProg(name, fileDir, "Taipei-Torrent",
		fmt.Sprintf("-port=%v", port),
		fmt.Sprintf("-fileDir=%v", fileDir),
		fmt.Sprintf("-seedRatio=%v", ratio),
		torrentFile)
}

func createTorrentFile(torrentFileName, root, announcePath string) (err error) {
	var metaInfo *torrent.MetaInfo
	metaInfo, err = torrent.CreateMetaInfoFromFileSystem(nil, root, 0, false)
	if err != nil {
		return
	}
	metaInfo.Announce = "http://127.0.0.1:8080/announce"
	metaInfo.CreatedBy = "testSwarm"
	var torrentFile *os.File
	torrentFile, err = os.Create(torrentFileName)
	if err != nil {
		return
	}
	defer torrentFile.Close()
	err = metaInfo.Bencode(torrentFile)
	if err != nil {
		return
	}
	return
}

func createDataFile(name string, length int64) (err error) {
	if (length & 3) != 0 {
		return fmt.Errorf("createDataFile only supports length that is a multiple of 4. Not %d", length)
	}
	var file *os.File
	file, err = os.Create(name)
	if err != nil {
		return
	}
	defer file.Close()
	err = file.Truncate(length)
	if err != nil {
		return
	}
	w := bufio.NewWriter(file)
	b := make([]byte, 4)
	for i := int64(0); i < length; i += 4 {
		b[0] = byte(i >> 24)
		b[1] = byte(i >> 16)
		b[2] = byte(i >> 8)
		b[3] = byte(i)
		_, err = w.Write(b)
		if err != nil {
			return
		}
	}
	return
}

func compareData(sourceName, copyDirName string) (err error) {
	_, base := path.Split(sourceName)
	copyName := path.Join(copyDirName, base)
	err = compare(sourceName, copyName)
	return
}

// Compare two files (or directories) for equality.
func compare(aName, bName string) (err error) {
	var aFileInfo, bFileInfo os.FileInfo
	aFileInfo, err = os.Stat(aName)
	if err != nil {
		return
	}
	bFileInfo, err = os.Stat(bName)
	if err != nil {
		return
	}
	aIsDir, bIsDir := aFileInfo.IsDir(), bFileInfo.IsDir()
	if aIsDir != bIsDir {
		return fmt.Errorf("%s.IsDir() == %v != %s.IsDir() == %v",
			aName, aIsDir,
			bName, bIsDir)
	}
	var aFile, bFile *os.File
	aFile, err = os.Open(aName)
	if err != nil {
		return
	}
	defer aFile.Close()
	bFile, err = os.Open(bName)
	if err != nil {
		return
	}
	defer bFile.Close()
	if !aIsDir {
		aSize, bSize := aFileInfo.Size(), bFileInfo.Size()
		if aSize != bSize {
			return fmt.Errorf("%s.Size() == %v != %s.Size() == %v",
				aName, aSize,
				bName, bSize)
		}
		var aBuf, bBuf bytes.Buffer
		bufferSize := int64(128 * 1024)
		for i := int64(0); i < aSize; i += bufferSize {
			toRead := bufferSize
			remainder := aSize - i
			if toRead > remainder {
				toRead = remainder
			}
			_, err = io.CopyN(&aBuf, aFile, toRead)
			if err != nil {
				return
			}
			_, err = io.CopyN(&bBuf, bFile, toRead)
			if err != nil {
				return
			}
			aBytes, bBytes := aBuf.Bytes(), bBuf.Bytes()
			for j := int64(0); j < toRead; j++ {
				a, b := aBytes[j], bBytes[j]
				if a != b {
					err = fmt.Errorf("%s[%d] %d != %d", aName, i+j, a, b)
					return
				}
			}
			aBuf.Reset()
			bBuf.Reset()
		}
	} else {
		var aNames, bNames []string
		aNames, err = aFile.Readdirnames(0)
		if err != nil {
			return
		}
		bNames, err = bFile.Readdirnames(0)
		if err != nil {
			return
		}
		if len(aNames) != len(bName) {
			err = fmt.Errorf("Directories %v and %v don't contain same number of files %d != %d",
				aName, bName, len(aNames), len(bNames))
		}
		for _, name := range aNames {
			err = compare(path.Join(aName, name), path.Join(bName, name))
			if err != nil {
				return
			}
		}
	}
	return
}

// type logWriter

type logWriter string

func (l logWriter) Write(p []byte) (n int, err error) {
	log.Println(l, string(p))
	n = len(p)
	return
}

// A test that's used to run multiple processes. From http://golang.org/src/pkg/os/exec/exec_test.go

func helperCommands(s ...string) *exec.Cmd {
	cs := []string{"-test.run=TestHelperProcess", "--"}
	cs = append(cs, s...)
	cmd := exec.Command(os.Args[0], cs...)
	cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
	return cmd
}

func TestHelperProcess(*testing.T) {
	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
		return
	}

	defer os.Exit(0)

	args := os.Args
	for len(args) > 0 {
		if args[0] == "--" {
			args = args[1:]
			break
		}
		args = args[1:]
	}

	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "No commands\n")
		os.Exit(2)
	}

	cmd, args := args[0], args[1:]
	switch cmd {
	case "tracker":
		fmt.Fprintf(os.Stderr, "tracker not implemented\n")
		os.Exit(2)
	case "client":
		fmt.Fprintf(os.Stderr, "client not implemented\n")
		os.Exit(2)
	default:
		fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
		os.Exit(2)
	}
}
<commit_msg>tweak naming<commit_after>package tracker

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/jackpal/Taipei-Torrent/torrent"
	"io"
	"io/ioutil"
	"log"
	"math"
	"os"
	"os/exec"
	"path"
	"testing"
	"time"
)

func TestScrapeURL(t *testing.T) {
	tests := []struct{ announce, scrape string }{
		{"", ""},
		{"foo", ""},
		{"x/announce", "x/scrape"},
		{"x/announce?ad#3", "x/scrape?ad#3"},
		{"announce/x", ""},
	}
	for _, test := range tests {
		scrape := ScrapePattern(test.announce)
		if scrape != test.scrape {
			t.Errorf("ScrapeURL(%#v) = %#v. Expected %#v", test.announce, scrape, test.scrape)
		}
	}
}

func TestSwarm1(t *testing.T) {
	testSwarm(t, 1)
}

func TestSwarm10(t *testing.T) {
	testSwarm(t, 10)
}

/* Larger sizes don't work correctly.

func TestSwarm20(t *testing.T) {
	testSwarm(t, 20)
}

func TestSwarm50(t *testing.T) {
	testSwarm(t, 50)
}

func TestSwarm100(t *testing.T) {
	testSwarm(t, 100)
}

*/

func testSwarm(t *testing.T, leechCount int) {
	err := runSwarm(leechCount)
	if err != nil {
		t.Fatal("Error running testSwarm", err)
	}
}

type prog struct {
	instanceName string
	dirName      string
	cmd          *exec.Cmd
}

func (p *prog) start(doneCh chan *prog) (err error) {
	log.Println("starting", p.instanceName)
	out := logWriter(p.instanceName)
	p.cmd.Stdout = &out
	p.cmd.Stderr = &out
	err = p.cmd.Start()
	if err != nil {
		return
	}
	go func() {
		p.cmd.Wait()
		doneCh <- p
	}()
	return
}

func (p *prog) kill() (err error) {
	err = p.cmd.Process.Kill()
	return
}

func NewProg(instanceName string, dir string, name string, arg ...string) (p *prog) {
	cmd := exec.Command(name, arg...)
	return &prog{instanceName: instanceName, dirName: dir, cmd: cmd}
}

func runSwarm(leechCount int) (err error) {
	var rootDir string
	rootDir, err = ioutil.TempDir("", "swarm")
	if err != nil {
		return
	}
	log.Printf("Temporary directory: %s", rootDir)
	seedDir := path.Join(rootDir, "seed")
	err = os.Mkdir(seedDir, 0700)
	if err != nil {
		return
	}
	seedData := path.Join(seedDir, "data")
	err = createDataFile(seedData, 1024*1024)
	if err != nil {
		return
	}
	torrentFile := path.Join(rootDir, "testSwarm.torrent")
	err = createTorrentFile(torrentFile, seedData, "127.0.0.1:8080/announce")
	if err != nil {
		return
	}

	doneCh := make(chan *prog, 1)

	tracker := newTracker("tracker", ":8080", rootDir, torrentFile)
	err = tracker.start(doneCh)
	if err != nil {
		return
	}
	defer tracker.kill()
	time.Sleep(100 * time.Microsecond)

	var seed, leech *prog
	seed = newTorrentClient("seed", 7000, torrentFile, seedDir, math.Inf(0))
	err = seed.start(doneCh)
	if err != nil {
		return
	}
	defer seed.kill()
	time.Sleep(50 * time.Microsecond)

	for l := 0; l < leechCount; l++ {
		leechDir := path.Join(rootDir, fmt.Sprintf("leech %d", l))
		err = os.Mkdir(leechDir, 0700)
		if err != nil {
			return
		}
		leech = newTorrentClient(fmt.Sprintf("leech %d", l), 7001+l, torrentFile, leechDir, 0)
		err = leech.start(doneCh)
		if err != nil {
			return
		}
		defer leech.kill()
	}

	timeout := make(chan bool, 1)
	go func() {
		// It takes about 3.5 seconds to complete the test on my computer.
		time.Sleep(50 * time.Second)
		timeout <- true
	}()

	for doneCount := 0; doneCount < leechCount; doneCount++ {
		select {
		case <-timeout:
			err = fmt.Errorf("Timout exceeded")
		case donePeer := <-doneCh:
			if donePeer == tracker || donePeer == seed {
				err = fmt.Errorf("%v finished before all leeches. Should not have.", donePeer)
			}
			err = compareData(seedData, donePeer.dirName)
		}
		if err != nil {
			return
		}
		log.Printf("Done: %d of %d", (doneCount + 1), leechCount)
	}
	if err != nil {
		return
	}
	// All is good. Clean up
	os.RemoveAll(rootDir)

	return
}

func newTracker(name string, addr string, fileDir string, torrentFile string) (p *prog) {
	return NewProg(name, fileDir, "Taipei-Torrent",
		fmt.Sprintf("-createTracker=%v", addr),
		fmt.Sprintf("-fileDir=%v", fileDir),
		torrentFile)
}

func newTorrentClient(name string, port int, torrentFile string, fileDir string, ratio float64) (p *prog) {
	return NewProg(name, fileDir, "Taipei-Torrent",
		fmt.Sprintf("-port=%v", port),
		fmt.Sprintf("-fileDir=%v", fileDir),
		fmt.Sprintf("-seedRatio=%v", ratio),
		torrentFile)
}

func createTorrentFile(torrentFileName, root, announcePath string) (err error) {
	var metaInfo *torrent.MetaInfo
	metaInfo, err = torrent.CreateMetaInfoFromFileSystem(nil, root, 0, false)
	if err != nil {
		return
	}
	metaInfo.Announce = "http://127.0.0.1:8080/announce"
	metaInfo.CreatedBy = "testSwarm"
	var torrentFile *os.File
	torrentFile, err = os.Create(torrentFileName)
	if err != nil {
		return
	}
	defer torrentFile.Close()
	err = metaInfo.Bencode(torrentFile)
	if err != nil {
		return
	}
	return
}

func createDataFile(name string, length int64) (err error) {
	if (length & 3) != 0 {
		return fmt.Errorf("createDataFile only supports length that is a multiple of 4. Not %d", length)
	}
	var file *os.File
	file, err = os.Create(name)
	if err != nil {
		return
	}
	defer file.Close()
	err = file.Truncate(length)
	if err != nil {
		return
	}
	w := bufio.NewWriter(file)
	b := make([]byte, 4)
	for i := int64(0); i < length; i += 4 {
		b[0] = byte(i >> 24)
		b[1] = byte(i >> 16)
		b[2] = byte(i >> 8)
		b[3] = byte(i)
		_, err = w.Write(b)
		if err != nil {
			return
		}
	}
	return
}

func compareData(sourceName, copyDirName string) (err error) {
	_, base := path.Split(sourceName)
	copyName := path.Join(copyDirName, base)
	err = compare(sourceName, copyName)
	return
}

// Compare two files (or directories) for equality.
func compare(aName, bName string) (err error) {
	var aFileInfo, bFileInfo os.FileInfo
	aFileInfo, err = os.Stat(aName)
	if err != nil {
		return
	}
	bFileInfo, err = os.Stat(bName)
	if err != nil {
		return
	}
	aIsDir, bIsDir := aFileInfo.IsDir(), bFileInfo.IsDir()
	if aIsDir != bIsDir {
		return fmt.Errorf("%s.IsDir() == %v != %s.IsDir() == %v",
			aName, aIsDir,
			bName, bIsDir)
	}
	var aFile, bFile *os.File
	aFile, err = os.Open(aName)
	if err != nil {
		return
	}
	defer aFile.Close()
	bFile, err = os.Open(bName)
	if err != nil {
		return
	}
	defer bFile.Close()
	if !aIsDir {
		aSize, bSize := aFileInfo.Size(), bFileInfo.Size()
		if aSize != bSize {
			return fmt.Errorf("%s.Size() == %v != %s.Size() == %v",
				aName, aSize,
				bName, bSize)
		}
		var aBuf, bBuf bytes.Buffer
		bufferSize := int64(128 * 1024)
		for i := int64(0); i < aSize; i += bufferSize {
			toRead := bufferSize
			remainder := aSize - i
			if toRead > remainder {
				toRead = remainder
			}
			_, err = io.CopyN(&aBuf, aFile, toRead)
			if err != nil {
				return
			}
			_, err = io.CopyN(&bBuf, bFile, toRead)
			if err != nil {
				return
			}
			aBytes, bBytes := aBuf.Bytes(), bBuf.Bytes()
			for j := int64(0); j < toRead; j++ {
				a, b := aBytes[j], bBytes[j]
				if a != b {
					err = fmt.Errorf("%s[%d] %d != %d", aName, i+j, a, b)
					return
				}
			}
			aBuf.Reset()
			bBuf.Reset()
		}
	} else {
		var aNames, bNames []string
		aNames, err = aFile.Readdirnames(0)
		if err != nil {
			return
		}
		bNames, err = bFile.Readdirnames(0)
		if err != nil {
			return
		}
		if len(aNames) != len(bName) {
			err = fmt.Errorf("Directories %v and %v don't contain same number of files %d != %d",
				aName, bName, len(aNames), len(bNames))
		}
		for _, name := range aNames {
			err = compare(path.Join(aName, name), path.Join(bName, name))
			if err != nil {
				return
			}
		}
	}
	return
}

// type logWriter

type logWriter string

func (l logWriter) Write(p []byte) (n int, err error) {
	log.Println(l, string(p))
	n = len(p)
	return
}

// A test that's used to run multiple processes. From http://golang.org/src/pkg/os/exec/exec_test.go

func helperCommands(s ...string) *exec.Cmd {
	cs := []string{"-test.run=TestHelperProcess", "--"}
	cs = append(cs, s...)
	cmd := exec.Command(os.Args[0], cs...)
	cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
	return cmd
}

func TestHelperProcess(*testing.T) {
	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
		return
	}

	defer os.Exit(0)

	args := os.Args
	for len(args) > 0 {
		if args[0] == "--" {
			args = args[1:]
			break
		}
		args = args[1:]
	}

	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "No commands\n")
		os.Exit(2)
	}

	cmd, args := args[0], args[1:]
	switch cmd {
	case "tracker":
		fmt.Fprintf(os.Stderr, "tracker not implemented\n")
		os.Exit(2)
	case "client":
		fmt.Fprintf(os.Stderr, "client not implemented\n")
		os.Exit(2)
	default:
		fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
		os.Exit(2)
	}
}
<|endoftext|>


<commit_before>package lfs

import (
	"path/filepath"
	"sync"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

const (
	batchSize = 100
)

type Transferable interface {
	Check() (*objectResource, *WrappedError)
	Transfer(CopyCallback) *WrappedError
	Object() *objectResource
	Oid() string
	Size() int64
	Name() string
	SetObject(*objectResource)
}

// TransferQueue provides a queue that will allow concurrent transfers.
type TransferQueue struct {
	meter         *ProgressMeter
	workers       int // Number of transfer workers to spawn
	transferKind  string
	errors        []*WrappedError
	transferables map[string]Transferable
	batcher       *Batcher
	apic          chan Transferable  // Channel for processing individual API requests
	transferc     chan Transferable  // Channel for processing transfers
	errorc        chan *WrappedError // Channel for processing errors
	watchers      []chan string
	wait          sync.WaitGroup
}

// newTransferQueue builds a TransferQueue, allowing `workers` concurrent transfers.
func newTransferQueue(files int, size int64, dryRun bool) *TransferQueue {
	q := &TransferQueue{
		meter:         NewProgressMeter(files, size, dryRun),
		apic:          make(chan Transferable, batchSize),
		transferc:     make(chan Transferable, batchSize),
		errorc:        make(chan *WrappedError),
		workers:       Config.ConcurrentTransfers(),
		transferables: make(map[string]Transferable),
	}

	q.run()

	return q
}

// Add adds a Transferable to the transfer queue.
func (q *TransferQueue) Add(t Transferable) {
	q.wait.Add(1)
	q.transferables[t.Oid()] = t

	if q.batcher != nil {
		q.batcher.Add(t)
		return
	}

	q.apic <- t
}

// Wait waits for the queue to finish processing all transfers
func (q *TransferQueue) Wait() {
	if q.batcher != nil {
		q.batcher.Exit()
	}

	q.wait.Wait()
	close(q.apic)
	close(q.transferc)
	close(q.errorc)

	for _, watcher := range q.watchers {
		close(watcher)
	}

	q.meter.Finish()
}

// Watch returns a channel where the queue will write the OID of each transfer
// as it completes. The channel will be closed when the queue finishes processing.
func (q *TransferQueue) Watch() chan string {
	c := make(chan string, batchSize)
	q.watchers = append(q.watchers, c)
	return c
}

// individualApiRoutine processes the queue of transfers one at a time by making
// a POST call for each object, feeding the results to the transfer workers.
// If configured, the object transfers can still happen concurrently, the
// sequential nature here is only for the meta POST calls.
func (q *TransferQueue) individualApiRoutine(apiWaiter chan interface{}) {
	for t := range q.apic {
		obj, err := t.Check()
		if err != nil {
			q.errorc <- err
			q.wait.Done()
			continue
		}

		if apiWaiter != nil { // Signal to launch more individual api workers
			q.meter.Start()
			select {
			case apiWaiter <- 1:
			default:
			}
		}

		if obj != nil {
			t.SetObject(obj)
			q.meter.Add(t.Name())
			q.transferc <- t
		} else {
			q.meter.Skip(t.Size())
			q.wait.Done()
		}
	}
}

// legacyFallback is used when a batch request is made to a server that does
// not support the batch endpoint. When this happens, the Transferables are
// fed from the batcher into apic to be processed individually.
func (q *TransferQueue) legacyFallback(failedBatch []Transferable) {
	tracerx.Printf("tq: batch api not implemented, falling back to individual")

	q.launchIndividualApiRoutines()

	for _, t := range failedBatch {
		q.apic <- t
	}

	for {
		batch := q.batcher.Next()
		if batch == nil {
			break
		}

		for _, t := range batch {
			q.apic <- t
		}
	}
}

// batchApiRoutine processes the queue of transfers using the batch endpoint,
// making only one POST call for all objects. The results are then handed
// off to the transfer workers.
func (q *TransferQueue) batchApiRoutine() {
	var startProgress sync.Once

	for {
		batch := q.batcher.Next()
		if batch == nil {
			break
		}

		tracerx.Printf("tq: sending batch of size %d", len(batch))

		transfers := make([]*objectResource, 0, len(batch))
		for _, t := range batch {
			transfers = append(transfers, &objectResource{Oid: t.Oid(), Size: t.Size()})
		}

		objects, err := Batch(transfers, q.transferKind)
		if err != nil {
			if isNotImplError(err) {
				configFile := filepath.Join(LocalGitDir, "config")
				git.Config.SetLocal(configFile, "lfs.batch", "false")

				go q.legacyFallback(batch)
				return
			}
			q.errorc <- err
			continue
		}

		startProgress.Do(q.meter.Start)

		for _, o := range objects {
			if _, ok := o.Rel(q.transferKind); ok {
				// This object has an error
				if o.Error != nil {
					q.errorc <- Error(o.Error)
					q.meter.Skip(o.Size)
					q.wait.Done()
					continue
				}

				// This object needs to be transferred
				if transfer, ok := q.transferables[o.Oid]; ok {
					transfer.SetObject(o)
					q.meter.Add(transfer.Name())
					q.transferc <- transfer
				} else {
					q.meter.Skip(transfer.Size())
					q.wait.Done()
				}
			} else {
				q.meter.Skip(o.Size)
				q.wait.Done()
			}
		}
	}
}

// This goroutine collects errors returned from transfers
func (q *TransferQueue) errorCollector() {
	for err := range q.errorc {
		q.errors = append(q.errors, err)
	}
}

func (q *TransferQueue) transferWorker() {
	for transfer := range q.transferc {
		cb := func(total, read int64, current int) error {
			q.meter.TransferBytes(q.transferKind, transfer.Name(), read, total, current)
			return nil
		}

		if err := transfer.Transfer(cb); err != nil {
			q.errorc <- err
		} else {
			oid := transfer.Oid()
			for _, c := range q.watchers {
				c <- oid
			}
		}

		q.meter.FinishTransfer(transfer.Name())

		q.wait.Done()
	}
}

// launchIndividualApiRoutines first launches a single api worker. When it
// receives the first successful api request it launches workers - 1 more
// workers. This prevents being prompted for credentials multiple times at once
// when they're needed.
func (q *TransferQueue) launchIndividualApiRoutines() {
	go func() {
		apiWaiter := make(chan interface{})
		go q.individualApiRoutine(apiWaiter)

		<-apiWaiter

		for i := 0; i < q.workers-1; i++ {
			go q.individualApiRoutine(nil)
		}
	}()
}

// run starts the transfer queue, doing individual or batch transfers depending
// on the Config.BatchTransfer() value. run will transfer files sequentially or
// concurrently depending on the Config.ConcurrentTransfers() value.
func (q *TransferQueue) run() {
	go q.errorCollector()

	tracerx.Printf("tq: starting %d transfer workers", q.workers)
	for i := 0; i < q.workers; i++ {
		go q.transferWorker()
	}

	if Config.BatchTransfer() {
		tracerx.Printf("tq: running as batched queue, batch size of %d", batchSize)
		q.batcher = NewBatcher(batchSize)
		go q.batchApiRoutine()
	} else {
		tracerx.Printf("tq: running as individual queue")
		q.launchIndividualApiRoutines()
	}
}

// Errors returns any errors encountered during transfer.
func (q *TransferQueue) Errors() []*WrappedError {
	return q.errors
}
<commit_msg>Decrease the waitgroup when batch errors<commit_after>package lfs

import (
	"path/filepath"
	"sync"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

const (
	batchSize = 100
)

type Transferable interface {
	Check() (*objectResource, *WrappedError)
	Transfer(CopyCallback) *WrappedError
	Object() *objectResource
	Oid() string
	Size() int64
	Name() string
	SetObject(*objectResource)
}

// TransferQueue provides a queue that will allow concurrent transfers.
type TransferQueue struct {
	meter         *ProgressMeter
	workers       int // Number of transfer workers to spawn
	transferKind  string
	errors        []*WrappedError
	transferables map[string]Transferable
	batcher       *Batcher
	apic          chan Transferable  // Channel for processing individual API requests
	transferc     chan Transferable  // Channel for processing transfers
	errorc        chan *WrappedError // Channel for processing errors
	watchers      []chan string
	wait          sync.WaitGroup
}

// newTransferQueue builds a TransferQueue, allowing `workers` concurrent transfers.
func newTransferQueue(files int, size int64, dryRun bool) *TransferQueue {
	q := &TransferQueue{
		meter:         NewProgressMeter(files, size, dryRun),
		apic:          make(chan Transferable, batchSize),
		transferc:     make(chan Transferable, batchSize),
		errorc:        make(chan *WrappedError),
		workers:       Config.ConcurrentTransfers(),
		transferables: make(map[string]Transferable),
	}

	q.run()

	return q
}

// Add adds a Transferable to the transfer queue.
func (q *TransferQueue) Add(t Transferable) {
	q.wait.Add(1)
	q.transferables[t.Oid()] = t

	if q.batcher != nil {
		q.batcher.Add(t)
		return
	}

	q.apic <- t
}

// Wait waits for the queue to finish processing all transfers
func (q *TransferQueue) Wait() {
	if q.batcher != nil {
		q.batcher.Exit()
	}

	q.wait.Wait()
	close(q.apic)
	close(q.transferc)
	close(q.errorc)

	for _, watcher := range q.watchers {
		close(watcher)
	}

	q.meter.Finish()
}

// Watch returns a channel where the queue will write the OID of each transfer
// as it completes. The channel will be closed when the queue finishes processing.
func (q *TransferQueue) Watch() chan string {
	c := make(chan string, batchSize)
	q.watchers = append(q.watchers, c)
	return c
}

// individualApiRoutine processes the queue of transfers one at a time by making
// a POST call for each object, feeding the results to the transfer workers.
// If configured, the object transfers can still happen concurrently, the
// sequential nature here is only for the meta POST calls.
func (q *TransferQueue) individualApiRoutine(apiWaiter chan interface{}) {
	for t := range q.apic {
		obj, err := t.Check()
		if err != nil {
			q.errorc <- err
			q.wait.Done()
			continue
		}

		if apiWaiter != nil { // Signal to launch more individual api workers
			q.meter.Start()
			select {
			case apiWaiter <- 1:
			default:
			}
		}

		if obj != nil {
			t.SetObject(obj)
			q.meter.Add(t.Name())
			q.transferc <- t
		} else {
			q.meter.Skip(t.Size())
			q.wait.Done()
		}
	}
}

// legacyFallback is used when a batch request is made to a server that does
// not support the batch endpoint. When this happens, the Transferables are
// fed from the batcher into apic to be processed individually.
func (q *TransferQueue) legacyFallback(failedBatch []Transferable) {
	tracerx.Printf("tq: batch api not implemented, falling back to individual")

	q.launchIndividualApiRoutines()

	for _, t := range failedBatch {
		q.apic <- t
	}

	for {
		batch := q.batcher.Next()
		if batch == nil {
			break
		}

		for _, t := range batch {
			q.apic <- t
		}
	}
}

// batchApiRoutine processes the queue of transfers using the batch endpoint,
// making only one POST call for all objects. The results are then handed
// off to the transfer workers.
func (q *TransferQueue) batchApiRoutine() {
	var startProgress sync.Once

	for {
		batch := q.batcher.Next()
		if batch == nil {
			break
		}

		tracerx.Printf("tq: sending batch of size %d", len(batch))

		transfers := make([]*objectResource, 0, len(batch))
		for _, t := range batch {
			transfers = append(transfers, &objectResource{Oid: t.Oid(), Size: t.Size()})
		}

		objects, err := Batch(transfers, q.transferKind)
		if err != nil {
			if isNotImplError(err) {
				configFile := filepath.Join(LocalGitDir, "config")
				git.Config.SetLocal(configFile, "lfs.batch", "false")

				go q.legacyFallback(batch)
				return
			}

			q.wait.Add(-len(transfers))

			q.errorc <- err
			continue
		}

		startProgress.Do(q.meter.Start)

		for _, o := range objects {
			if _, ok := o.Rel(q.transferKind); ok {
				// This object has an error
				if o.Error != nil {
					q.errorc <- Error(o.Error)
					q.meter.Skip(o.Size)
					q.wait.Done()
					continue
				}

				// This object needs to be transferred
				if transfer, ok := q.transferables[o.Oid]; ok {
					transfer.SetObject(o)
					q.meter.Add(transfer.Name())
					q.transferc <- transfer
				} else {
					q.meter.Skip(transfer.Size())
					q.wait.Done()
				}
			} else {
				q.meter.Skip(o.Size)
				q.wait.Done()
			}
		}
	}
}

// This goroutine collects errors returned from transfers
func (q *TransferQueue) errorCollector() {
	for err := range q.errorc {
		q.errors = append(q.errors, err)
	}
}

func (q *TransferQueue) transferWorker() {
	for transfer := range q.transferc {
		cb := func(total, read int64, current int) error {
			q.meter.TransferBytes(q.transferKind, transfer.Name(), read, total, current)
			return nil
		}

		if err := transfer.Transfer(cb); err != nil {
			q.errorc <- err
		} else {
			oid := transfer.Oid()
			for _, c := range q.watchers {
				c <- oid
			}
		}

		q.meter.FinishTransfer(transfer.Name())

		q.wait.Done()
	}
}

// launchIndividualApiRoutines first launches a single api worker. When it
// receives the first successful api request it launches workers - 1 more
// workers. This prevents being prompted for credentials multiple times at once
// when they're needed.
func (q *TransferQueue) launchIndividualApiRoutines() {
	go func() {
		apiWaiter := make(chan interface{})
		go q.individualApiRoutine(apiWaiter)

		<-apiWaiter

		for i := 0; i < q.workers-1; i++ {
			go q.individualApiRoutine(nil)
		}
	}()
}

// run starts the transfer queue, doing individual or batch transfers depending
// on the Config.BatchTransfer() value. run will transfer files sequentially or
// concurrently depending on the Config.ConcurrentTransfers() value.
func (q *TransferQueue) run() {
	go q.errorCollector()

	tracerx.Printf("tq: starting %d transfer workers", q.workers)
	for i := 0; i < q.workers; i++ {
		go q.transferWorker()
	}

	if Config.BatchTransfer() {
		tracerx.Printf("tq: running as batched queue, batch size of %d", batchSize)
		q.batcher = NewBatcher(batchSize)
		go q.batchApiRoutine()
	} else {
		tracerx.Printf("tq: running as individual queue")
		q.launchIndividualApiRoutines()
	}
}

// Errors returns any errors encountered during transfer.
func (q *TransferQueue) Errors() []*WrappedError {
	return q.errors
}
<|endoftext|>


<commit_before>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/log/log.go                                   |
|                                                          |
| LastModified: Feb 21, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package log

import (
	"context"
	"fmt"
	"unsafe"

	"github.com/hprose/hprose-golang/v3/rpc/core"
)

type Log struct {
	Println func(v interface{})
	Enabled bool
}

// NewLog returns a Log instance.
func NewLog(f ...func(v interface{})) *Log {
	p := func(v interface{}) { fmt.Println(v) }
	if len(f) > 0 {
		p = f[0]
	}
	return &Log{
		Println: p,
		Enabled: true,
	}
}

func unsafeString(bytes []byte) string {
	return *(*string)(unsafe.Pointer(&bytes))
}

// IOHandler for log.
func (log *Log) IOHandler(ctx context.Context, request []byte, next core.NextIOHandler) (response []byte, err error) {
	enabled := log.Enabled
	if context, ok := core.FromContext(ctx); ok {
		enabled = context.Items().GetBool("log", enabled)
	}
	if !enabled {
		return next(ctx, request)
	}
	defer func() {
		if e := recover(); e != nil {
			log.Println(e)
			panic(e)
		}
		if err != nil {
			log.Println(err)
		} else {
			log.Println(unsafeString(response))
		}
	}()
	log.Println(unsafeString(request))
	return next(ctx, request)
}

var log = NewLog()

// IOHandler is the default io handler for log.
func IOHandler(ctx context.Context, request []byte, next core.NextIOHandler) (response []byte, err error) {
	return log.IOHandler(ctx, request, next)
}
<commit_msg>Add InvokeHandler for log.<commit_after>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/log/log.go                                   |
|                                                          |
| LastModified: Feb 27, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package log

import (
	"context"
	"encoding/json"
	"fmt"
	"unsafe"

	"github.com/hprose/hprose-golang/v3/rpc/core"
)

// Log plugin for hprose.
type Log struct {
	Println func(v ...interface{})
	Enabled bool
}

// NewLog returns a Log instance.
func NewLog(f ...func(v ...interface{})) *Log {
	p := func(v ...interface{}) { fmt.Println(v...) }
	if len(f) > 0 {
		p = f[0]
	}
	return &Log{
		Println: p,
		Enabled: true,
	}
}

func unsafeString(bytes []byte) string {
	return *(*string)(unsafe.Pointer(&bytes))
}

func (log *Log) isEnabled(ctx context.Context) (enabled bool) {
	enabled = log.Enabled
	if context, ok := core.FromContext(ctx); ok {
		enabled = context.Items().GetBool("log", enabled)
	}
	return
}

// IOHandler for log.
func (log *Log) IOHandler(ctx context.Context, request []byte, next core.NextIOHandler) (response []byte, err error) {
	if !log.isEnabled(ctx) {
		return next(ctx, request)
	}
	defer func() {
		if e := recover(); e != nil {
			log.Println("panic:", e)
			panic(e)
		}
		if err != nil {
			log.Println("error:", err)
		} else {
			log.Println("response:", unsafeString(response))
		}
	}()
	log.Println("request:", unsafeString(request))
	return next(ctx, request)
}

// InvokeHandler for log.
func (log *Log) InvokeHandler(ctx context.Context, name string, args []interface{}, next core.NextInvokeHandler) (result []interface{}, err error) {
	if !log.isEnabled(ctx) {
		return next(ctx, name, args)
	}
	defer func() {
		if e := recover(); e != nil {
			log.Println("panic:", e)
			panic(e)
		}
		if err != nil {
			log.Println("error:", err)
		} else if data, e := json.Marshal(result); e == nil {
			log.Println("result:", unsafeString(data))
		} else {
			log.Println("result:", result)
		}
	}()
	log.Println("name:", name)
	if data, e := json.Marshal(args); e == nil {
		log.Println("args:", unsafeString(data))
	} else {
		log.Println("args:", args)
	}
	return next(ctx, name, args)
}

var log = NewLog()

// IOHandler is the default io handler for log.
func IOHandler(ctx context.Context, request []byte, next core.NextIOHandler) (response []byte, err error) {
	return log.IOHandler(ctx, request, next)
}

// InvokeHandler is the default io handler for log.
func InvokeHandler(ctx context.Context, name string, args []interface{}, next core.NextInvokeHandler) (result []interface{}, err error) {
	return log.InvokeHandler(ctx, name, args, next)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/Randomsock5/Randomsocks5/cipherPipe"
	"github.com/Randomsock5/Randomsocks5/simpleSocks5"
	"github.com/Randomsock5/Randomsocks5/tool"
	"golang.org/x/crypto/poly1305"
	"crypto/cipher"
	"crypto/sha256"
	"crypto/sha512"
	"flag"
	"github.com/codahale/chacha20"
	"io"
	"log"
	"net"
	"os"
	"strconv"
)

var (
	addr   string
	port   int
	passwd string
)

func init() {
	flag.StringVar(&addr, "addr", ConnDefaultAddr, "Set Listen Addr")
	flag.IntVar(&port, "port", ConnDefaultPort, "Set Server Port")
	flag.StringVar(&passwd, "passwd", DefaultPasswd, "Set Passwd")
}

func main() {
	flag.Parse()

	l, err := net.Listen(ConnDefaultType, addr+":"+strconv.Itoa(port))
	if err != nil {
		log.Println("Error listening: ", err)
		os.Exit(1)
	}
	defer l.Close()

	for {
		// Listen for an incoming connection.
		conn, err := l.Accept()
		if err != nil {
			log.Println("Error accepting:  ", err)
			continue
		}
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	go func ()  {
		defer conn.Close()

		timeCookie := tool.GetTimeCookie()
		initKey := sha256.Sum256([]byte(passwd+timeCookie))
		nonce := sha512.Sum512([]byte(timeCookie + passwd))

		es, err := chacha20.NewXChaCha(initKey[:], nonce[:XNonceSize])
		ds, err := chacha20.NewXChaCha(initKey[:], nonce[:XNonceSize])
		if err != nil {
			log.Println("Error chacha20 init:  ", err)
			return
		}

		//random data head length
		randomDataLen, _ := tool.ReadInt(initKey[len(initKey)-2:])
		if randomDataLen < 32767 {
			randomDataLen = randomDataLen + 2984
		}

		finish := make(chan struct{})
		go proxy(conn, es, ds, finish, randomDataLen, &initKey)

		select {
		case  <- finish:
			return
		}
	}()
}

func proxy( conn net.Conn, encodeStm, decodeStm cipher.Stream,finish chan struct{}, randomDataLen int, key *[32]byte) {
	der, dew := cipherPipe.Pipe(decodeStm)
	defer der.Close()
	defer dew.Close()
	enr, enw := cipherPipe.Pipe(encodeStm)
	defer enr.Close()
	defer enw.Close()

	go io.Copy(dew, conn)

	// read random data head
	var ri = 0
	var randomdata = make([]byte, randomDataLen + poly1305.TagSize)
	for ri < (randomDataLen + poly1305.TagSize) {
		r, err := der.Read(randomdata[ri:])
		if err != nil {
			close(finish)
			return
		}
		ri += r
	}

	var mac [16]byte
	copy(mac[:],randomdata[randomDataLen:])
	if !poly1305.Verify(&mac, randomdata[:randomDataLen], key) {
		log.Println("poly1305 mac verify error")
		log.Println(randomdata[randomDataLen:])
		close(finish)
		return
	}

	go io.Copy(conn, enr)

	simpleSocks5.Socks5Handle(der, enw)
  close(finish)
}
<commit_msg>remove log<commit_after>package main

import (
	"github.com/Randomsock5/Randomsocks5/cipherPipe"
	"github.com/Randomsock5/Randomsocks5/simpleSocks5"
	"github.com/Randomsock5/Randomsocks5/tool"
	"golang.org/x/crypto/poly1305"
	"crypto/cipher"
	"crypto/sha256"
	"crypto/sha512"
	"flag"
	"github.com/codahale/chacha20"
	"io"
	"log"
	"net"
	"os"
	"strconv"
)

var (
	addr   string
	port   int
	passwd string
)

func init() {
	flag.StringVar(&addr, "addr", ConnDefaultAddr, "Set Listen Addr")
	flag.IntVar(&port, "port", ConnDefaultPort, "Set Server Port")
	flag.StringVar(&passwd, "passwd", DefaultPasswd, "Set Passwd")
}

func main() {
	flag.Parse()

	l, err := net.Listen(ConnDefaultType, addr+":"+strconv.Itoa(port))
	if err != nil {
		log.Println("Error listening: ", err)
		os.Exit(1)
	}
	defer l.Close()

	for {
		// Listen for an incoming connection.
		conn, err := l.Accept()
		if err != nil {
			log.Println("Error accepting:  ", err)
			continue
		}
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	go func ()  {
		defer conn.Close()

		timeCookie := tool.GetTimeCookie()
		initKey := sha256.Sum256([]byte(passwd+timeCookie))
		nonce := sha512.Sum512([]byte(timeCookie + passwd))

		es, err := chacha20.NewXChaCha(initKey[:], nonce[:XNonceSize])
		ds, err := chacha20.NewXChaCha(initKey[:], nonce[:XNonceSize])
		if err != nil {
			log.Println("Error chacha20 init:  ", err)
			return
		}

		//random data head length
		randomDataLen, _ := tool.ReadInt(initKey[len(initKey)-2:])
		if randomDataLen < 32767 {
			randomDataLen = randomDataLen + 2984
		}

		finish := make(chan struct{})
		go proxy(conn, es, ds, finish, randomDataLen, &initKey)

		select {
		case  <- finish:
			return
		}
	}()
}

func proxy( conn net.Conn, encodeStm, decodeStm cipher.Stream,finish chan struct{}, randomDataLen int, key *[32]byte) {
	der, dew := cipherPipe.Pipe(decodeStm)
	defer der.Close()
	defer dew.Close()
	enr, enw := cipherPipe.Pipe(encodeStm)
	defer enr.Close()
	defer enw.Close()

	go io.Copy(dew, conn)

	// read random data head
	var ri = 0
	var randomdata = make([]byte, randomDataLen + poly1305.TagSize)
	for ri < (randomDataLen + poly1305.TagSize) {
		r, err := der.Read(randomdata[ri:])
		if err != nil {
			close(finish)
			return
		}
		ri += r
	}

	var mac [16]byte
	copy(mac[:],randomdata[randomDataLen:])
	if !poly1305.Verify(&mac, randomdata[:randomDataLen], key) {
		log.Println("poly1305 mac verify error")
		close(finish)
		return
	}

	go io.Copy(conn, enr)

	simpleSocks5.Socks5Handle(der, enw)
  close(finish)
}
<|endoftext|>