<commit_before>package env

import (
	"fmt"
	"io"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/xo/terminfo"
	"github.com/xo/usql/text"
)

// Vars is a map of variables.
type Vars map[string]string

// Set sets a variable name.
func (v Vars) Set(name, value string) {
	v[name] = value
}

// Unset unsets a variable name.
func (v Vars) Unset(name string) {
	delete(v, name)
}

// All returns all variables as a map.
func (v Vars) All() map[string]string {
	return map[string]string(v)
}

var vars, pvars Vars

func init() {
	// get USQL_* variables
	enableHostInformation := "true"
	if v := Getenv("USQL_SHOW_HOST_INFORMATION"); v != "" {
		enableHostInformation = v
	}
	// get color level
	colorLevel, _ := terminfo.ColorLevelFromEnv()
	enableSyntaxHL := "true"
	if colorLevel < terminfo.ColorLevelBasic {
		enableSyntaxHL = "false"
	}
	vars = Vars{
		// usql related logic
		"SHOW_HOST_INFORMATION": enableHostInformation,
		"PAGER":                 Getenv("USQL_PAGER", "PAGER"),
		// syntax highlighting variables
		"SYNTAX_HL":             enableSyntaxHL,
		"SYNTAX_HL_FORMAT":      colorLevel.ChromaFormatterName(),
		"SYNTAX_HL_STYLE":       "monokai",
		"SYNTAX_HL_OVERRIDE_BG": "true",
	}
	pvars = Vars{
		"border":                   "1",
		"columns":                  "0",
		"csv_fieldsep":             ",",
		"expanded":                 "off",
		"fieldsep":                 "|",
		"fieldsep_zero":            "off",
		"footer":                   "on",
		"format":                   "aligned",
		"linestyle":                "ascii",
		"null":                     "",
		"numericlocale":            "off",
		"pager":                    "off",
		"pager_min_lines":          "0",
		"recordsep":                "\n",
		"recordsep_zero":           "off",
		"tableattr":                "",
		"time":                     "RFC3339Nano",
		"title":                    "",
		"tuples_only":              "off",
		"unicode_border_linestyle": "single",
		"unicode_column_linestyle": "single",
		"unicode_header_linestyle": "single",
	}
}

// ValidIdentifier returns an error when n is not a valid identifier.
func ValidIdentifier(n string) error {
	r := []rune(n)
	rlen := len(r)
	if rlen < 1 {
		return text.ErrInvalidIdentifier
	}
	for i := 0; i < rlen; i++ {
		if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) {
			return text.ErrInvalidIdentifier
		}
	}
	return nil
}

// Set sets a variable.
func Set(name, value string) error {
	err := ValidIdentifier(name)
	if err != nil {
		return err
	}
	switch name {
	case "QUIET":
		if value == "" {
			value = "on"
		} else {
			value, err = ParseBool(value, name)
			if err != nil {
				return err
			}
		}
	}
	vars.Set(name, value)
	return nil
}

// Unset unsets a variable.
func Unset(name string) error {
	if err := ValidIdentifier(name); err != nil {
		return err
	}
	vars.Unset(name)
	return nil
}

// All returns all variables.
func All() Vars {
	m := make(Vars)
	for k, v := range vars {
		m[k] = v
	}
	return m
}

// Pall returns all p variables.
func Pall() Vars {
	m := make(Vars)
	for k, v := range pvars {
		m[k] = v
	}
	return m
}

// Pwrite writes the p variables to the writer.
func Pwrite(w io.Writer) error {
	keys := make([]string, len(pvars))
	var i, width int
	for k := range pvars {
		keys[i], width = k, max(len(k), width)
		i++
	}
	sort.Strings(keys)
	for _, k := range keys {
		val := pvars[k]
		switch k {
		case "csv_fieldsep", "fieldsep", "recordsep", "null":
			val = strconv.QuoteToASCII(val)
		case "tableattr", "title":
			if val != "" {
				val = strconv.QuoteToASCII(val)
			}
		}
		fmt.Fprintln(w, k+strings.Repeat(" ", width-len(k)), val)
	}
	return nil
}

var (
	formatRE    = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json|vertical)$`)
	linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`)
	borderRE    = regexp.MustCompile(`^(single|double)$`)
)

func ParseBool(value, name string) (string, error) {
	switch strings.ToLower(value) {
	case "1", "t", "tr", "tru", "true", "on":
		return "on", nil
	case "0", "f", "fa", "fal", "fals", "false", "of", "off":
		return "off", nil
	}
	return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean")
}

func ParseKeywordBool(value, name string, keywords ...string) (string, error) {
	v := strings.ToLower(value)
	switch v {
	case "1", "t", "tr", "tru", "true", "on":
		return "on", nil
	case "0", "f", "fa", "fal", "fals", "false", "of", "off":
		return "off", nil
	}
	for _, k := range keywords {
		if v == k {
			return v, nil
		}
	}
	return "", fmt.Errorf(text.FormatFieldInvalid, value, name)
}

func Get(name string) string {
	return vars[name]
}

func Pget(name string) (string, error) {
	v, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	return v, nil
}

// Ptoggle toggles a p variable.
func Ptoggle(name, extra string) (string, error) {
	_, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	switch name {
	case "border", "columns", "pager_min_lines":
	case "pager":
		switch pvars[name] {
		case "on", "always":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "expanded":
		switch pvars[name] {
		case "on", "auto":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
		switch pvars[name] {
		case "on":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "format":
		switch {
		case extra != "" && pvars[name] != extra:
			pvars[name] = extra
		case pvars[name] == "aligned":
			pvars[name] = "unaligned"
		default:
			pvars[name] = "aligned"
		}
	case "linestyle":
	case "csv_fieldsep", "fieldsep", "null", "recordsep", "time":
	case "tableattr", "title":
		pvars[name] = ""
	case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
	default:
		panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
	}
	return pvars[name], nil
}

// Pset sets a p variable.
func Pset(name, value string) (string, error) {
	_, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	switch name {
	case "border", "columns", "pager_min_lines":
		i, _ := strconv.Atoi(value)
		pvars[name] = fmt.Sprintf("%d", i)
	case "pager":
		s, err := ParseKeywordBool(value, name, "always")
		if err != nil {
			return "", text.ErrInvalidFormatExpandedType
		}
		pvars[name] = s
	case "expanded":
		s, err := ParseKeywordBool(value, name, "auto")
		if err != nil {
			return "", text.ErrInvalidFormatExpandedType
		}
		pvars[name] = s
	case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
		s, err := ParseBool(value, name)
		if err != nil {
			return "", err
		}
		pvars[name] = s
	case "format":
		if !formatRE.MatchString(value) {
			return "", text.ErrInvalidFormatType
		}
		pvars[name] = value
	case "linestyle":
		if !linestlyeRE.MatchString(value) {
			return "", text.ErrInvalidFormatLineStyle
		}
		pvars[name] = value
	case "csv_fieldsep", "fieldsep", "null", "recordsep", "tableattr", "time", "title":
		pvars[name] = value
	case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
		if !borderRE.MatchString(value) {
			return "", text.ErrInvalidFormatBorderLineStyle
		}
		pvars[name] = value
	default:
		panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
	}
	return pvars[name], nil
}

// timeConstMap is the time const name to value map.
var timeConstMap = map[string]string{
	"ANSIC":       time.ANSIC,
	"UnixDate":    time.UnixDate,
	"RubyDate":    time.RubyDate,
	"RFC822":      time.RFC822,
	"RFC822Z":     time.RFC822Z,
	"RFC850":      time.RFC850,
	"RFC1123":     time.RFC1123,
	"RFC1123Z":    time.RFC1123Z,
	"RFC3339":     time.RFC3339,
	"RFC3339Nano": time.RFC3339Nano,
	"Kitchen":     time.Kitchen,
	"Stamp":       time.Stamp,
	"StampMilli":  time.StampMilli,
	"StampMicro":  time.StampMicro,
	"StampNano":   time.StampNano,
}

// GoTime returns the user's time format converted to Go's time.Format value.
func GoTime() string {
	tfmt := pvars["time"]
	if s, ok := timeConstMap[tfmt]; ok {
		return s
	}
	return tfmt
}

// max returns maximum of a, b.
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
<commit_msg>Enabling p variable pager=on by default when less or more is in $PATH<commit_after>package env

import (
	"fmt"
	"io"
	"os/exec"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/xo/terminfo"
	"github.com/xo/usql/text"
)

// Vars is a map of variables.
type Vars map[string]string

// Set sets a variable name.
func (v Vars) Set(name, value string) {
	v[name] = value
}

// Unset unsets a variable name.
func (v Vars) Unset(name string) {
	delete(v, name)
}

// All returns all variables as a map.
func (v Vars) All() map[string]string {
	return map[string]string(v)
}

var vars, pvars Vars

func init() {
	// get USQL_* variables
	enableHostInformation := "true"
	if v := Getenv("USQL_SHOW_HOST_INFORMATION"); v != "" {
		enableHostInformation = v
	}
	// get color level
	colorLevel, _ := terminfo.ColorLevelFromEnv()
	enableSyntaxHL := "true"
	if colorLevel < terminfo.ColorLevelBasic {
		enableSyntaxHL = "false"
	}
	pager, pagerCmd := "off", Getenv("USQL_PAGER", "PAGER")
	if pagerCmd == "" {
		for _, s := range []string{"less", "more"} {
			if _, err := exec.LookPath(s); err != nil {
				pagerCmd = s
				break
			}
		}
	}
	if pagerCmd != "" {
		pager = "on"
	}
	vars = Vars{
		// usql related logic
		"SHOW_HOST_INFORMATION": enableHostInformation,
		"PAGER":                 pagerCmd,
		// syntax highlighting variables
		"SYNTAX_HL":             enableSyntaxHL,
		"SYNTAX_HL_FORMAT":      colorLevel.ChromaFormatterName(),
		"SYNTAX_HL_STYLE":       "monokai",
		"SYNTAX_HL_OVERRIDE_BG": "true",
	}
	pvars = Vars{
		"border":                   "1",
		"columns":                  "0",
		"csv_fieldsep":             ",",
		"expanded":                 "off",
		"fieldsep":                 "|",
		"fieldsep_zero":            "off",
		"footer":                   "on",
		"format":                   "aligned",
		"linestyle":                "ascii",
		"null":                     "",
		"numericlocale":            "off",
		"pager":                    pager,
		"pager_min_lines":          "0",
		"recordsep":                "\n",
		"recordsep_zero":           "off",
		"tableattr":                "",
		"time":                     "RFC3339Nano",
		"title":                    "",
		"tuples_only":              "off",
		"unicode_border_linestyle": "single",
		"unicode_column_linestyle": "single",
		"unicode_header_linestyle": "single",
	}
}

// ValidIdentifier returns an error when n is not a valid identifier.
func ValidIdentifier(n string) error {
	r := []rune(n)
	rlen := len(r)
	if rlen < 1 {
		return text.ErrInvalidIdentifier
	}
	for i := 0; i < rlen; i++ {
		if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) {
			return text.ErrInvalidIdentifier
		}
	}
	return nil
}

// Set sets a variable.
func Set(name, value string) error {
	err := ValidIdentifier(name)
	if err != nil {
		return err
	}
	switch name {
	case "QUIET":
		if value == "" {
			value = "on"
		} else {
			value, err = ParseBool(value, name)
			if err != nil {
				return err
			}
		}
	}
	vars.Set(name, value)
	return nil
}

// Unset unsets a variable.
func Unset(name string) error {
	if err := ValidIdentifier(name); err != nil {
		return err
	}
	vars.Unset(name)
	return nil
}

// All returns all variables.
func All() Vars {
	m := make(Vars)
	for k, v := range vars {
		m[k] = v
	}
	return m
}

// Pall returns all p variables.
func Pall() Vars {
	m := make(Vars)
	for k, v := range pvars {
		m[k] = v
	}
	return m
}

// Pwrite writes the p variables to the writer.
func Pwrite(w io.Writer) error {
	keys := make([]string, len(pvars))
	var i, width int
	for k := range pvars {
		keys[i], width = k, max(len(k), width)
		i++
	}
	sort.Strings(keys)
	for _, k := range keys {
		val := pvars[k]
		switch k {
		case "csv_fieldsep", "fieldsep", "recordsep", "null":
			val = strconv.QuoteToASCII(val)
		case "tableattr", "title":
			if val != "" {
				val = strconv.QuoteToASCII(val)
			}
		}
		fmt.Fprintln(w, k+strings.Repeat(" ", width-len(k)), val)
	}
	return nil
}

var (
	formatRE    = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json|vertical)$`)
	linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`)
	borderRE    = regexp.MustCompile(`^(single|double)$`)
)

func ParseBool(value, name string) (string, error) {
	switch strings.ToLower(value) {
	case "1", "t", "tr", "tru", "true", "on":
		return "on", nil
	case "0", "f", "fa", "fal", "fals", "false", "of", "off":
		return "off", nil
	}
	return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean")
}

func ParseKeywordBool(value, name string, keywords ...string) (string, error) {
	v := strings.ToLower(value)
	switch v {
	case "1", "t", "tr", "tru", "true", "on":
		return "on", nil
	case "0", "f", "fa", "fal", "fals", "false", "of", "off":
		return "off", nil
	}
	for _, k := range keywords {
		if v == k {
			return v, nil
		}
	}
	return "", fmt.Errorf(text.FormatFieldInvalid, value, name)
}

func Get(name string) string {
	return vars[name]
}

func Pget(name string) (string, error) {
	v, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	return v, nil
}

// Ptoggle toggles a p variable.
func Ptoggle(name, extra string) (string, error) {
	_, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	switch name {
	case "border", "columns", "pager_min_lines":
	case "pager":
		switch pvars[name] {
		case "on", "always":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "expanded":
		switch pvars[name] {
		case "on", "auto":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
		switch pvars[name] {
		case "on":
			pvars[name] = "off"
		case "off":
			pvars[name] = "on"
		default:
			panic(fmt.Sprintf("invalid state for field %s", name))
		}
	case "format":
		switch {
		case extra != "" && pvars[name] != extra:
			pvars[name] = extra
		case pvars[name] == "aligned":
			pvars[name] = "unaligned"
		default:
			pvars[name] = "aligned"
		}
	case "linestyle":
	case "csv_fieldsep", "fieldsep", "null", "recordsep", "time":
	case "tableattr", "title":
		pvars[name] = ""
	case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
	default:
		panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
	}
	return pvars[name], nil
}

// Pset sets a p variable.
func Pset(name, value string) (string, error) {
	_, ok := pvars[name]
	if !ok {
		return "", fmt.Errorf(text.UnknownFormatFieldName, name)
	}
	switch name {
	case "border", "columns", "pager_min_lines":
		i, _ := strconv.Atoi(value)
		pvars[name] = fmt.Sprintf("%d", i)
	case "pager":
		s, err := ParseKeywordBool(value, name, "always")
		if err != nil {
			return "", text.ErrInvalidFormatExpandedType
		}
		pvars[name] = s
	case "expanded":
		s, err := ParseKeywordBool(value, name, "auto")
		if err != nil {
			return "", text.ErrInvalidFormatExpandedType
		}
		pvars[name] = s
	case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only":
		s, err := ParseBool(value, name)
		if err != nil {
			return "", err
		}
		pvars[name] = s
	case "format":
		if !formatRE.MatchString(value) {
			return "", text.ErrInvalidFormatType
		}
		pvars[name] = value
	case "linestyle":
		if !linestlyeRE.MatchString(value) {
			return "", text.ErrInvalidFormatLineStyle
		}
		pvars[name] = value
	case "csv_fieldsep", "fieldsep", "null", "recordsep", "tableattr", "time", "title":
		pvars[name] = value
	case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle":
		if !borderRE.MatchString(value) {
			return "", text.ErrInvalidFormatBorderLineStyle
		}
		pvars[name] = value
	default:
		panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name))
	}
	return pvars[name], nil
}

// timeConstMap is the time const name to value map.
var timeConstMap = map[string]string{
	"ANSIC":       time.ANSIC,
	"UnixDate":    time.UnixDate,
	"RubyDate":    time.RubyDate,
	"RFC822":      time.RFC822,
	"RFC822Z":     time.RFC822Z,
	"RFC850":      time.RFC850,
	"RFC1123":     time.RFC1123,
	"RFC1123Z":    time.RFC1123Z,
	"RFC3339":     time.RFC3339,
	"RFC3339Nano": time.RFC3339Nano,
	"Kitchen":     time.Kitchen,
	"Stamp":       time.Stamp,
	"StampMilli":  time.StampMilli,
	"StampMicro":  time.StampMicro,
	"StampNano":   time.StampNano,
}

// GoTime returns the user's time format converted to Go's time.Format value.
func GoTime() string {
	tfmt := pvars["time"]
	if s, ok := timeConstMap[tfmt]; ok {
		return s
	}
	return tfmt
}

// max returns maximum of a, b.
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"path"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder {
	req := NewRequest(t, "GET", path.Join(user, repo))
	resp := session.MakeRequest(t, req, http.StatusOK)

	// Click the PR button to create a pull
	htmlDoc := NewHTMLParser(t, resp.Body)
	link, exists := htmlDoc.doc.Find("#new-pull-request").Parent().Attr("href")
	assert.True(t, exists, "The template has changed")
	if branch != "master" {
		link = strings.Replace(link, ":master", ":"+branch, 1)
	}

	req = NewRequest(t, "GET", link)
	resp = session.MakeRequest(t, req, http.StatusOK)

	// Submit the form for creating the pull
	htmlDoc = NewHTMLParser(t, resp.Body)
	link, exists = htmlDoc.doc.Find("form.ui.form").Attr("action")
	assert.True(t, exists, "The template has changed")
	req = NewRequestWithValues(t, "POST", link, map[string]string{
		"_csrf": htmlDoc.GetCSRF(),
		"title": title,
	})
	resp = session.MakeRequest(t, req, http.StatusFound)

	return resp
}

func TestPullCreate(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")

		// check the redirected URL
		url := resp.HeaderMap.Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)

		// check .diff can be accessed and matches performed change
		req := NewRequest(t, "GET", url+".diff")
		resp = session.MakeRequest(t, req, http.StatusOK)
		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
		assert.Regexp(t, "^diff", resp.Body)
		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one

		// check .patch can be accessed and matches performed change
		req = NewRequest(t, "GET", url+".patch")
		resp = session.MakeRequest(t, req, http.StatusOK)
		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
		assert.Regexp(t, "diff", resp.Body)
		assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body)
		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one
	})
}

func TestPullCreate_TitleEscape(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master", "XSS PR")

		// check the redirected URL
		url := resp.HeaderMap.Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)

		// Edit title
		req := NewRequest(t, "GET", url)
		resp = session.MakeRequest(t, req, http.StatusOK)
		htmlDoc := NewHTMLParser(t, resp.Body)
		editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url")
		assert.True(t, exists, "The template has changed")

		req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{
			"_csrf": htmlDoc.GetCSRF(),
			"title": "XSS PR",
		})
		session.MakeRequest(t, req, http.StatusOK)

		req = NewRequest(t, "GET", url)
		resp = session.MakeRequest(t, req, http.StatusOK)
		htmlDoc = NewHTMLParser(t, resp.Body)
		titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html()
		assert.NoError(t, err)
		assert.Equal(t, "<i>XSS PR</i>", titleHTML)
		titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
		assert.NoError(t, err)
		assert.Equal(t, "<u>XSS PR</u>", titleHTML)
	})
}

func testUIDeleteBranch(t *testing.T, session *TestSession, ownerName, repoName, branchName string) {
	relURL := "/" + path.Join(ownerName, repoName, "branches")
	req := NewRequest(t, "GET", relURL)
	resp := session.MakeRequest(t, req, http.StatusOK)
	htmlDoc := NewHTMLParser(t, resp.Body)

	req = NewRequestWithValues(t, "POST", relURL+"/delete", map[string]string{
		"_csrf": getCsrf(t, htmlDoc.doc),
		"name":  branchName,
	})
	session.MakeRequest(t, req, http.StatusOK)
}

func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoName string) {
	relURL := "/" + path.Join(ownerName, repoName, "settings")
	req := NewRequest(t, "GET", relURL)
	resp := session.MakeRequest(t, req, http.StatusOK)
	htmlDoc := NewHTMLParser(t, resp.Body)

	req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
		"_csrf":     getCsrf(t, htmlDoc.doc),
		"repo_name": repoName,
	})
	session.MakeRequest(t, req, http.StatusFound)
}

func TestPullBranchDelete(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		defer prepareTestEnv(t)()

		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusFound)
		testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title")

		// check the redirected URL
		url := resp.HeaderMap.Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
		req := NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)

		// delete head branch and confirm pull page is ok
		testUIDeleteBranch(t, session, "user1", "repo1", "master1")
		req = NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)

		// delete head repository and confirm pull page is ok
		testDeleteRepository(t, session, "user1", "repo1")
		req = NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)
	})
}
<commit_msg>integrations: deprecate HeaderMaps (#11018)<commit_after>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"path"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder {
	req := NewRequest(t, "GET", path.Join(user, repo))
	resp := session.MakeRequest(t, req, http.StatusOK)

	// Click the PR button to create a pull
	htmlDoc := NewHTMLParser(t, resp.Body)
	link, exists := htmlDoc.doc.Find("#new-pull-request").Parent().Attr("href")
	assert.True(t, exists, "The template has changed")
	if branch != "master" {
		link = strings.Replace(link, ":master", ":"+branch, 1)
	}

	req = NewRequest(t, "GET", link)
	resp = session.MakeRequest(t, req, http.StatusOK)

	// Submit the form for creating the pull
	htmlDoc = NewHTMLParser(t, resp.Body)
	link, exists = htmlDoc.doc.Find("form.ui.form").Attr("action")
	assert.True(t, exists, "The template has changed")
	req = NewRequestWithValues(t, "POST", link, map[string]string{
		"_csrf": htmlDoc.GetCSRF(),
		"title": title,
	})
	resp = session.MakeRequest(t, req, http.StatusFound)

	return resp
}

func TestPullCreate(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")

		// check the redirected URL
		url := resp.Header().Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)

		// check .diff can be accessed and matches performed change
		req := NewRequest(t, "GET", url+".diff")
		resp = session.MakeRequest(t, req, http.StatusOK)
		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
		assert.Regexp(t, "^diff", resp.Body)
		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one

		// check .patch can be accessed and matches performed change
		req = NewRequest(t, "GET", url+".patch")
		resp = session.MakeRequest(t, req, http.StatusOK)
		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body)
		assert.Regexp(t, "diff", resp.Body)
		assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body)
		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one
	})
}

func TestPullCreate_TitleEscape(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master", "XSS PR")

		// check the redirected URL
		url := resp.Header().Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)

		// Edit title
		req := NewRequest(t, "GET", url)
		resp = session.MakeRequest(t, req, http.StatusOK)
		htmlDoc := NewHTMLParser(t, resp.Body)
		editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url")
		assert.True(t, exists, "The template has changed")

		req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{
			"_csrf": htmlDoc.GetCSRF(),
			"title": "XSS PR",
		})
		session.MakeRequest(t, req, http.StatusOK)

		req = NewRequest(t, "GET", url)
		resp = session.MakeRequest(t, req, http.StatusOK)
		htmlDoc = NewHTMLParser(t, resp.Body)
		titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html()
		assert.NoError(t, err)
		assert.Equal(t, "<i>XSS PR</i>", titleHTML)
		titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
		assert.NoError(t, err)
		assert.Equal(t, "<u>XSS PR</u>", titleHTML)
	})
}

func testUIDeleteBranch(t *testing.T, session *TestSession, ownerName, repoName, branchName string) {
	relURL := "/" + path.Join(ownerName, repoName, "branches")
	req := NewRequest(t, "GET", relURL)
	resp := session.MakeRequest(t, req, http.StatusOK)
	htmlDoc := NewHTMLParser(t, resp.Body)

	req = NewRequestWithValues(t, "POST", relURL+"/delete", map[string]string{
		"_csrf": getCsrf(t, htmlDoc.doc),
		"name":  branchName,
	})
	session.MakeRequest(t, req, http.StatusOK)
}

func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoName string) {
	relURL := "/" + path.Join(ownerName, repoName, "settings")
	req := NewRequest(t, "GET", relURL)
	resp := session.MakeRequest(t, req, http.StatusOK)
	htmlDoc := NewHTMLParser(t, resp.Body)

	req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
		"_csrf":     getCsrf(t, htmlDoc.doc),
		"repo_name": repoName,
	})
	session.MakeRequest(t, req, http.StatusFound)
}

func TestPullBranchDelete(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		defer prepareTestEnv(t)()

		session := loginUser(t, "user1")
		testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
		testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusFound)
		testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
		resp := testPullCreate(t, session, "user1", "repo1", "master1", "This is a pull title")

		// check the redirected URL
		url := resp.Header().Get("Location")
		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
		req := NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)

		// delete head branch and confirm pull page is ok
		testUIDeleteBranch(t, session, "user1", "repo1", "master1")
		req = NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)

		// delete head repository and confirm pull page is ok
		testDeleteRepository(t, session, "user1", "repo1")
		req = NewRequest(t, "GET", url)
		session.MakeRequest(t, req, http.StatusOK)
	})
}
<|endoftext|>


<commit_before>package dev

import (
	"io"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RegisterTestServer is a basic register server that in addition also can
// signal when a read or write has completed.
type RegisterTestServer interface {
	RegisterServer
	ReadExecuted()
	WriteExecuted()
}

// RegisterServerBasic represents a single State register.
type RegisterServerBasic struct {
	sync.RWMutex
	state State

	readExecutedChan  chan struct{}
	writeExecutedChan chan struct{}
}

// NewRegisterBasic returns a new basic register server.
func NewRegisterBasic() *RegisterServerBasic {
	return &RegisterServerBasic{
		// Use an appropriate larger buffer size if we construct test
		// scenarios where it's needed.
		writeExecutedChan: make(chan struct{}, 32),
		readExecutedChan:  make(chan struct{}, 32),
	}
}

// NewRegisterBasicWithState returns a new basic register server with an initial
// state set.
func NewRegisterBasicWithState(state *State) *RegisterServerBasic {
	return &RegisterServerBasic{
		state: *state,
		// Use an appropriate larger buffer size if we construct test
		// scenarios where it's needed.
		writeExecutedChan: make(chan struct{}, 32),
		readExecutedChan:  make(chan struct{}, 32),
	}
}

func (r *RegisterServerBasic) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	r.RLock()
	defer r.RUnlock()
	r.readExecutedChan <- struct{}{}
	return &State{Value: r.state.Value, Timestamp: r.state.Timestamp}, nil
}

func (r *RegisterServerBasic) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	r.Lock()
	defer r.Unlock()
	writeResp := &WriteResponse{}
	if s.Timestamp > r.state.Timestamp {
		r.state = *s
		writeResp.New = true
	}
	r.writeExecutedChan <- struct{}{}
	return writeResp, nil
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerBasic) WriteAsync(stream Register_WriteAsyncServer) error {
	for {
		state, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&Empty{})
		}
		if err != nil {
			return err
		}
		_, err = r.Write(context.Background(), state)
		if err != nil {
			return err
		}
	}
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerBasic) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerBasic) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	err := rrts.Send(&r.state)
	if err != nil {
		return err
	}
	return nil
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerBasic) ReadExecuted() {
	<-r.readExecutedChan
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerBasic) WriteExecuted() {
	<-r.writeExecutedChan
}

// RegisterServerError represents a register server that for any of its methods
// always returns an error.
type RegisterServerError struct {
	err error
}

// NewRegisterError returns a new error register server.
func NewRegisterError(err error) *RegisterServerError {
	return &RegisterServerError{
		err: err,
	}
}

func (r *RegisterServerError) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	return nil, r.err
}

func (r *RegisterServerError) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	return nil, r.err
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerError) WriteAsync(stream Register_WriteAsyncServer) error {
	return r.err
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerError) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerError) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	return r.err
}

// ReadExecuted never returns since r always returns an error for Read.
func (r *RegisterServerError) ReadExecuted() {
	<-make(chan struct{})
}

// WriteExecuted never returns since r always returns an error for Write.
func (r *RegisterServerError) WriteExecuted() {
	<-make(chan struct{})
}

// RegisterServerSlow represents a register server that for any of its methods
// waits a given duration before returing a reply.
type RegisterServerSlow struct {
	delay      time.Duration
	realServer RegisterTestServer
}

// NewRegisterSlow returns a new slow register server.
func NewRegisterSlow(dur time.Duration) *RegisterServerSlow {
	return &RegisterServerSlow{
		delay:      dur,
		realServer: NewRegisterBasic(),
	}
}

// NewRegisterSlowWithState returns a new slow register server with an initial
// state set.
func NewRegisterSlowWithState(dur time.Duration, state *State) *RegisterServerSlow {
	return &RegisterServerSlow{
		delay:      dur,
		realServer: NewRegisterBasicWithState(state),
	}
}

func (r *RegisterServerSlow) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	time.Sleep(r.delay)
	return r.realServer.Read(ctx, rq)
}

func (r *RegisterServerSlow) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	time.Sleep(r.delay)
	return r.realServer.Write(ctx, s)
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerSlow) WriteAsync(stream Register_WriteAsyncServer) error {
	// There are no replies to wait for.
	return r.realServer.WriteAsync(stream)
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerSlow) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	time.Sleep(r.delay)
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerSlow) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	panic("not implemented")
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerSlow) ReadExecuted() {
	r.realServer.ReadExecuted()
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerSlow) WriteExecuted() {
	r.realServer.WriteExecuted()
}

// RegisterServerBench represents a single State register used for benchmarking.
type RegisterServerBench struct {
	sync.RWMutex
	state State
}

// NewRegisterBench returns a new register benchmark server.
func NewRegisterBench() *RegisterServerBench {
	return &RegisterServerBench{}
}

func (r *RegisterServerBench) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	r.RLock()
	defer r.RUnlock()
	return &State{Value: r.state.Value, Timestamp: r.state.Timestamp}, nil
}

func (r *RegisterServerBench) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	r.Lock()
	defer r.Unlock()
	writeResp := &WriteResponse{}
	if s.Timestamp > r.state.Timestamp {
		r.state = *s
		writeResp.New = true
	}
	return writeResp, nil
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerBench) WriteAsync(stream Register_WriteAsyncServer) error {
	for {
		state, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&Empty{})
		}
		if err != nil {
			return err
		}
		_, err = r.Write(context.Background(), state)
		if err != nil {
			return err
		}
	}
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerBench) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.ReadNoQC(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerBench) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	panic("not implemented")
}

// ReadExecuted is a no-op.
func (r *RegisterServerBench) ReadExecuted() {}

// WriteExecuted is no-op.
func (r *RegisterServerBench) WriteExecuted() {}

// RegisterServerLockedWithState represents a register server with an initial
// state that does not reply to any requests before it's unlocked.
type RegisterServerLockedWithState struct {
	lock              chan struct{}
	realServer        *RegisterServerBasic
	readTwoNumReplies int
	readTwoLockChan   chan struct{}
}

// NewRegisterServerLockedWithState returns a new locked register server with an initial state.
func NewRegisterServerLockedWithState(state *State, readTwoNumReplies int) *RegisterServerLockedWithState {
	return &RegisterServerLockedWithState{
		lock:              make(chan struct{}),
		realServer:        NewRegisterBasicWithState(state),
		readTwoNumReplies: readTwoNumReplies,
		readTwoLockChan:   make(chan struct{}, 1),
	}
}

func (r *RegisterServerLockedWithState) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	<-r.lock
	return r.realServer.Read(ctx, rq)
}

func (r *RegisterServerLockedWithState) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	<-r.lock
	return r.realServer.Write(ctx, s)
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) WriteAsync(stream Register_WriteAsyncServer) error {
	<-r.lock
	return r.realServer.WriteAsync(stream)
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	<-r.lock

	r.realServer.RLock()
	state := r.realServer.state
	r.realServer.RUnlock()

	for i := 0; i < r.readTwoNumReplies; i++ {
		<-r.readTwoLockChan
		err := rrts.Send(&state)
		if err != nil {
			return err
		}
	}

	return nil
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerLockedWithState) ReadExecuted() {
	r.realServer.ReadExecuted()
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerLockedWithState) WriteExecuted() {
	r.realServer.WriteExecuted()
}

// Unlock unlocks the register server.
func (r *RegisterServerLockedWithState) Unlock() {
	close(r.lock)
}

func (r *RegisterServerLockedWithState) PerformSingleReadTwo() {
	r.readTwoLockChan <- struct{}{}
}
<commit_msg>dev: simplify a server handler<commit_after>package dev

import (
	"io"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RegisterTestServer is a basic register server that in addition also can
// signal when a read or write has completed.
type RegisterTestServer interface {
	RegisterServer
	ReadExecuted()
	WriteExecuted()
}

// RegisterServerBasic represents a single State register.
type RegisterServerBasic struct {
	sync.RWMutex
	state State

	readExecutedChan  chan struct{}
	writeExecutedChan chan struct{}
}

// NewRegisterBasic returns a new basic register server.
func NewRegisterBasic() *RegisterServerBasic {
	return &RegisterServerBasic{
		// Use an appropriate larger buffer size if we construct test
		// scenarios where it's needed.
		writeExecutedChan: make(chan struct{}, 32),
		readExecutedChan:  make(chan struct{}, 32),
	}
}

// NewRegisterBasicWithState returns a new basic register server with an initial
// state set.
func NewRegisterBasicWithState(state *State) *RegisterServerBasic {
	return &RegisterServerBasic{
		state: *state,
		// Use an appropriate larger buffer size if we construct test
		// scenarios where it's needed.
		writeExecutedChan: make(chan struct{}, 32),
		readExecutedChan:  make(chan struct{}, 32),
	}
}

func (r *RegisterServerBasic) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	r.RLock()
	defer r.RUnlock()
	r.readExecutedChan <- struct{}{}
	return &State{Value: r.state.Value, Timestamp: r.state.Timestamp}, nil
}

func (r *RegisterServerBasic) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	r.Lock()
	defer r.Unlock()
	writeResp := &WriteResponse{}
	if s.Timestamp > r.state.Timestamp {
		r.state = *s
		writeResp.New = true
	}
	r.writeExecutedChan <- struct{}{}
	return writeResp, nil
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerBasic) WriteAsync(stream Register_WriteAsyncServer) error {
	for {
		state, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&Empty{})
		}
		if err != nil {
			return err
		}
		_, err = r.Write(context.Background(), state)
		if err != nil {
			return err
		}
	}
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerBasic) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerBasic) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	return rrts.Send(&r.state)
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerBasic) ReadExecuted() {
	<-r.readExecutedChan
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerBasic) WriteExecuted() {
	<-r.writeExecutedChan
}

// RegisterServerError represents a register server that for any of its methods
// always returns an error.
type RegisterServerError struct {
	err error
}

// NewRegisterError returns a new error register server.
func NewRegisterError(err error) *RegisterServerError {
	return &RegisterServerError{
		err: err,
	}
}

func (r *RegisterServerError) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	return nil, r.err
}

func (r *RegisterServerError) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	return nil, r.err
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerError) WriteAsync(stream Register_WriteAsyncServer) error {
	return r.err
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerError) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerError) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	return r.err
}

// ReadExecuted never returns since r always returns an error for Read.
func (r *RegisterServerError) ReadExecuted() {
	<-make(chan struct{})
}

// WriteExecuted never returns since r always returns an error for Write.
func (r *RegisterServerError) WriteExecuted() {
	<-make(chan struct{})
}

// RegisterServerSlow represents a register server that for any of its methods
// waits a given duration before returing a reply.
type RegisterServerSlow struct {
	delay      time.Duration
	realServer RegisterTestServer
}

// NewRegisterSlow returns a new slow register server.
func NewRegisterSlow(dur time.Duration) *RegisterServerSlow {
	return &RegisterServerSlow{
		delay:      dur,
		realServer: NewRegisterBasic(),
	}
}

// NewRegisterSlowWithState returns a new slow register server with an initial
// state set.
func NewRegisterSlowWithState(dur time.Duration, state *State) *RegisterServerSlow {
	return &RegisterServerSlow{
		delay:      dur,
		realServer: NewRegisterBasicWithState(state),
	}
}

func (r *RegisterServerSlow) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	time.Sleep(r.delay)
	return r.realServer.Read(ctx, rq)
}

func (r *RegisterServerSlow) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	time.Sleep(r.delay)
	return r.realServer.Write(ctx, s)
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerSlow) WriteAsync(stream Register_WriteAsyncServer) error {
	// There are no replies to wait for.
	return r.realServer.WriteAsync(stream)
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerSlow) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	time.Sleep(r.delay)
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerSlow) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	panic("not implemented")
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerSlow) ReadExecuted() {
	r.realServer.ReadExecuted()
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerSlow) WriteExecuted() {
	r.realServer.WriteExecuted()
}

// RegisterServerBench represents a single State register used for benchmarking.
type RegisterServerBench struct {
	sync.RWMutex
	state State
}

// NewRegisterBench returns a new register benchmark server.
func NewRegisterBench() *RegisterServerBench {
	return &RegisterServerBench{}
}

func (r *RegisterServerBench) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	r.RLock()
	defer r.RUnlock()
	return &State{Value: r.state.Value, Timestamp: r.state.Timestamp}, nil
}

func (r *RegisterServerBench) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	r.Lock()
	defer r.Unlock()
	writeResp := &WriteResponse{}
	if s.Timestamp > r.state.Timestamp {
		r.state = *s
		writeResp.New = true
	}
	return writeResp, nil
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerBench) WriteAsync(stream Register_WriteAsyncServer) error {
	for {
		state, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&Empty{})
		}
		if err != nil {
			return err
		}
		_, err = r.Write(context.Background(), state)
		if err != nil {
			return err
		}
	}
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerBench) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.ReadNoQC(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerBench) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	panic("not implemented")
}

// ReadExecuted is a no-op.
func (r *RegisterServerBench) ReadExecuted() {}

// WriteExecuted is no-op.
func (r *RegisterServerBench) WriteExecuted() {}

// RegisterServerLockedWithState represents a register server with an initial
// state that does not reply to any requests before it's unlocked.
type RegisterServerLockedWithState struct {
	lock              chan struct{}
	realServer        *RegisterServerBasic
	readTwoNumReplies int
	readTwoLockChan   chan struct{}
}

// NewRegisterServerLockedWithState returns a new locked register server with an initial state.
func NewRegisterServerLockedWithState(state *State, readTwoNumReplies int) *RegisterServerLockedWithState {
	return &RegisterServerLockedWithState{
		lock:              make(chan struct{}),
		realServer:        NewRegisterBasicWithState(state),
		readTwoNumReplies: readTwoNumReplies,
		readTwoLockChan:   make(chan struct{}, 1),
	}
}

func (r *RegisterServerLockedWithState) Read(ctx context.Context, rq *ReadRequest) (*State, error) {
	<-r.lock
	return r.realServer.Read(ctx, rq)
}

func (r *RegisterServerLockedWithState) Write(ctx context.Context, s *State) (*WriteResponse, error) {
	<-r.lock
	return r.realServer.Write(ctx, s)
}

// WriteAsync implements the WriteAsync method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) WriteAsync(stream Register_WriteAsyncServer) error {
	<-r.lock
	return r.realServer.WriteAsync(stream)
}

// ReadNoQC implements the ReadNoQC method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) ReadNoQC(ctx context.Context, rq *ReadRequest) (*State, error) {
	return r.Read(ctx, rq)
}

// ReadTwo implements the ReadTwo method from the RegisterServer interface.
func (r *RegisterServerLockedWithState) ReadTwo(rq *ReadRequest, rrts Register_ReadTwoServer) error {
	<-r.lock

	r.realServer.RLock()
	state := r.realServer.state
	r.realServer.RUnlock()

	for i := 0; i < r.readTwoNumReplies; i++ {
		<-r.readTwoLockChan
		err := rrts.Send(&state)
		if err != nil {
			return err
		}
	}

	return nil
}

// ReadExecuted returns when r has has completed a read.
func (r *RegisterServerLockedWithState) ReadExecuted() {
	r.realServer.ReadExecuted()
}

// WriteExecuted returns when r has has completed a write.
func (r *RegisterServerLockedWithState) WriteExecuted() {
	r.realServer.WriteExecuted()
}

// Unlock unlocks the register server.
func (r *RegisterServerLockedWithState) Unlock() {
	close(r.lock)
}

func (r *RegisterServerLockedWithState) PerformSingleReadTwo() {
	r.readTwoLockChan <- struct{}{}
}
<|endoftext|>


<commit_before>package hydra

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("FunctionMapper", func() {
	Context("FunctionMapper", func() {
		It("maps correct key to function", func() {
			fc := make(chan FunctionInfo, 3)
			fc <- NewFunctionInfo("a", nil, "", 1, PRODUCER)
			fc <- NewFunctionInfo("b", nil, "", 1, PRODUCER)
			fc <- NewFunctionInfo("c", nil, "", 1, PRODUCER)

			m := mapFunctions(3, fc)

			for k, v := range m {
				Expect(k).To(Equal(v.Info().Name()))
			}
		})

		It("maps all the parts to their consumers", func(){
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, FILTER)
			c := NewFunctionInfo("c", nil, "b", 1, CONSUMER)

			fc := make(chan FunctionInfo, 3)
			fc <- a
			fc <- b
			fc <- c

			m := mapFunctions(3, fc)

			Expect(len(m["a"].Consumers())).To(Equal(1))
			Expect(m["a"].Consumers()).To(ContainElement(b))
			Expect(len(m["b"].Consumers())).To(Equal(1))
			Expect(m["b"].Consumers()).To(ContainElement(c))
		})

		It("handles multiple consumers", func() {
			fc := make(chan FunctionInfo, 3)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, CONSUMER)
			c := NewFunctionInfo("c", nil, "a", 1, CONSUMER)

			fc <- a
			fc <- b
			fc <- c

			m := mapFunctions(3, fc)

			Expect(len(m["a"].Consumers())).To(Equal(2))
			Expect(m["a"].Consumers()).To(ContainElement(b))
			Expect(m["a"].Consumers()).To(ContainElement(c))
		})

		It("keeps only producers/filters that have a consumer", func() {
			fc := make(chan FunctionInfo, 4)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, CONSUMER)
			c := NewFunctionInfo("c", nil, "a", 1, FILTER)
			d := NewFunctionInfo("d", nil, "", 1, PRODUCER)

			fc <- a
			fc <- b
			fc <- c
			fc <- d

			m := mapFunctions(4, fc)

			Expect(len(m)).To(Equal(1))
			Expect(m["a"]).ToNot(BeNil())
		})

		It("panics with a name mismatch", func() {
			fc := make(chan FunctionInfo, 2)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "wrong", 1, CONSUMER)

			fc <- a
			fc <- b

			Expect(func() { mapFunctions(2, fc) }).To(Panic())
		})

		It("detects that the number of functions is wrong", func(done Done) {
			fc := make(chan FunctionInfo, 2)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "wrong", 1, CONSUMER)

			fc <- a
			fc <- b

			Expect(func() { mapFunctions(9, fc) }).To(Panic())
			close(done)
		}, 1)
	})
})
<commit_msg>Grammatical fixes in test statements.<commit_after>package hydra

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("FunctionMapper", func() {
	Context("FunctionMapper", func(){
		It("maps the correct key to the function", func(){
			fc := make(chan FunctionInfo, 3)
			fc <- NewFunctionInfo("a", nil, "", 1, PRODUCER)
			fc <- NewFunctionInfo("b", nil, "", 1, PRODUCER)
			fc <- NewFunctionInfo("c", nil, "", 1, PRODUCER)

			m := mapFunctions(3, fc)

			for k, v := range m {
				Expect(k).To(Equal(v.Info().Name()))
			}
		})

		It("maps each part to its consumer", func(){
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, FILTER)
			c := NewFunctionInfo("c", nil, "b", 1, CONSUMER)

			fc := make(chan FunctionInfo, 3)
			fc <- a
			fc <- b
			fc <- c

			m := mapFunctions(3, fc)

			Expect(len(m["a"].Consumers())).To(Equal(1))
			Expect(m["a"].Consumers()).To(ContainElement(b))
			Expect(len(m["b"].Consumers())).To(Equal(1))
			Expect(m["b"].Consumers()).To(ContainElement(c))
		})

		It("handles multiple consumers", func() {
			fc := make(chan FunctionInfo, 3)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, CONSUMER)
			c := NewFunctionInfo("c", nil, "a", 1, CONSUMER)

			fc <- a
			fc <- b
			fc <- c

			m := mapFunctions(3, fc)

			Expect(len(m["a"].Consumers())).To(Equal(2))
			Expect(m["a"].Consumers()).To(ContainElement(b))
			Expect(m["a"].Consumers()).To(ContainElement(c))
		})

		It("keeps only producers/filters that have a consumer", func() {
			fc := make(chan FunctionInfo, 4)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "a", 1, CONSUMER)
			c := NewFunctionInfo("c", nil, "a", 1, FILTER)
			d := NewFunctionInfo("d", nil, "", 1, PRODUCER)

			fc <- a
			fc <- b
			fc <- c
			fc <- d

			m := mapFunctions(4, fc)

			Expect(len(m)).To(Equal(1))
			Expect(m["a"]).ToNot(BeNil())
		})

		It("panics with a name mismatch", func() {
			fc := make(chan FunctionInfo, 2)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "wrong", 1, CONSUMER)

			fc <- a
			fc <- b

			Expect(func() { mapFunctions(2, fc) }).To(Panic())
		})

		It("detects that the number of functions is wrong", func(done Done) {
			fc := make(chan FunctionInfo, 2)
			a := NewFunctionInfo("a", nil, "", 1, PRODUCER)
			b := NewFunctionInfo("b", nil, "wrong", 1, CONSUMER)

			fc <- a
			fc <- b

			Expect(func() { mapFunctions(9, fc) }).To(Panic())
			close(done)
		}, 1)
	})
})
<|endoftext|>


<commit_before>// Copyright 2018 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package cluster

import (
	"errors"
	"testing"
)

func TestValidateReplicationSlots(t *testing.T) {
	tests := []struct {
		in  string
		err error
	}{
		{
			in: "goodslotname_434432",
		},
		{
			in:  "badslotname-34223",
			err: errors.New(`wrong replication slot name: "badslotname-34223"`),
		},
		{
			in:  "badslotname\n",
			err: errors.New(`wrong replication slot name: "badslotname\n"`),
		},
		{
			in:  " badslotname",
			err: errors.New(`wrong replication slot name: " badslotname"`),
		},
		{
			in:  "badslotname ",
			err: errors.New(`wrong replication slot name: "badslotname "`),
		},
		{
			in:  "stolon_c874a3cb",
			err: errors.New(`replication slot name is reserved: "stolon_c874a3cb"`),
		},
	}

	for i, tt := range tests {
		err := validateReplicationSlot(tt.in)

		if tt.err != nil {
			if err == nil {
				t.Errorf("#%d: got no error, wanted error: %v", i, tt.err)
			} else if tt.err.Error() != err.Error() {
				t.Errorf("#%d: got error: %v, wanted error: %v", i, err, tt.err)
			}
		} else {
			if err != nil {
				t.Errorf("#%d: unexpected error: %v", i, err)
			}
		}
	}
}
<commit_msg>Add test coverage for method FindDB<commit_after>// Copyright 2018 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package cluster

import (
	"errors"
	"testing"
)

func TestValidateReplicationSlots(t *testing.T) {
	tests := []struct {
		in  string
		err error
	}{
		{
			in: "goodslotname_434432",
		},
		{
			in:  "badslotname-34223",
			err: errors.New(`wrong replication slot name: "badslotname-34223"`),
		},
		{
			in:  "badslotname\n",
			err: errors.New(`wrong replication slot name: "badslotname\n"`),
		},
		{
			in:  " badslotname",
			err: errors.New(`wrong replication slot name: " badslotname"`),
		},
		{
			in:  "badslotname ",
			err: errors.New(`wrong replication slot name: "badslotname "`),
		},
		{
			in:  "stolon_c874a3cb",
			err: errors.New(`replication slot name is reserved: "stolon_c874a3cb"`),
		},
	}

	for i, tt := range tests {
		err := validateReplicationSlot(tt.in)

		if tt.err != nil {
			if err == nil {
				t.Errorf("#%d: got no error, wanted error: %v", i, tt.err)
			} else if tt.err.Error() != err.Error() {
				t.Errorf("#%d: got error: %v, wanted error: %v", i, err, tt.err)
			}
		} else {
			if err != nil {
				t.Errorf("#%d: unexpected error: %v", i, err)
			}
		}
	}
}

func TestClusterData_FindDB(t *testing.T) {
	db := DB{
		UID:  "dbUUID",
		Spec: &DBSpec{KeeperUID: "sameKeeperUUID"},
	}
	tests := []struct {
		name        string
		clusterData ClusterData
		keeper      *Keeper
		expectedDB  *DB
	}{
		{
			name:        "should return nil if the clusterData is empty",
			clusterData: ClusterData{},
			keeper:      &Keeper{},
			expectedDB:  nil,
		},
		{
			name: "should return nil if DB is not found for given keeper",
			clusterData: ClusterData{
				DBs: map[string]*DB{
					"dbUUID": &db,
				},
			},
			keeper:     &Keeper{UID: "differentUUID"},
			expectedDB: nil,
		},
		{
			name: "should return the DB if DB is found for given keeper",
			clusterData: ClusterData{
				DBs: map[string]*DB{
					"dbUUID": &db,
				},
			},
			keeper:     &Keeper{UID: "sameKeeperUUID"},
			expectedDB: &db,
		},
	}

	for _, tt := range tests {
		actual := tt.clusterData.FindDB(tt.keeper)
		if actual != tt.expectedDB {
			t.Errorf("Expected %v, but got %v", tt.expectedDB, actual)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build darwin
// +build !js
// +build !ios

package devicescale

// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework AppKit
//
// #import <AppKit/AppKit.h>
//
// static float scale() {
//   NSScreen* primary = [[NSScreen screens] firstObject];
//   return [primary backingScaleFactor];
// }
import "C"

func impl() float64 {
	return float64(C.scale())
}
<commit_msg>devicescale: Use mainScreen instead of screens[0] to get the 'current' screen state<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build darwin
// +build !js
// +build !ios

package devicescale

// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework AppKit
//
// #import <AppKit/AppKit.h>
//
// static float scale() {
//   NSScreen* primary = [NSScreen mainScreen];
//   return [primary backingScaleFactor];
// }
import "C"

func impl() float64 {
	return float64(C.scale())
}
<|endoftext|>


<commit_before>package operator

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"

	"github.com/turnage/redditproto"
)

// rcloser wraps a buffer so that it can passed as an io.ReadCloser.
type rcloser struct {
	*bytes.Buffer
}

func (b *rcloser) Close() error {
	return nil
}

type errReader struct{}

func (e *errReader) Read(p []byte) (int, error) {
	return 0, fmt.Errorf("an error")
}

func (e *errReader) Close() error {
	return nil
}

func TestParseLinkListing(t *testing.T) {
	if _, err := parseLinkListing(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseLinkListing(
		&rcloser{bytes.NewBufferString(`[]"`)},
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseLinkListing(
		&rcloser{bytes.NewBufferString(`{}`)},
	); err == nil {
		t.Errorf("wanted error for nil data")
	}

	if _, err := parseLinkListing(
		&rcloser{bytes.NewBufferString(`{"data": {}}`)},
	); err == nil {
		t.Errorf("wanted error for nil children")
	}

	links, err := parseLinkListing(&rcloser{bytes.NewBufferString(`{
		"data": {
			"children": [
				{"data": {"title": "hello"}},
				{"data": {"title": "hola"}},
				{"data": {"title": "bye"}}
			]
		}
	}`)})
	if err != nil {
		t.Fatalf("error: %v", err)
	}
	if len(links) != 3 {
		t.Errorf("wanted to find 3 links; resp is %v", links)
	}
}

func TestParseThread(t *testing.T) {
	if _, err := parseThread(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseThread(&errReader{}); err == nil {
		t.Errorf("Wanted error for failed read")
	}

	if _, err := parseThread(
		&rcloser{bytes.NewBufferString(`asds`)},
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseThread(
		&rcloser{bytes.NewBufferString(`[]`)},
	); err == nil {
		t.Errorf("wanted error for missing listings")
	}

	if _, err := parseThread(&rcloser{bytes.NewBufferString(`[
			{"data": {}},
			{"data": {}}
	]`)}); err == nil {
		t.Errorf("wanted error for bad link listing")
	}

	if _, err := parseThread(&rcloser{bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}},
					{"data": {"title": "bye"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {"id": "harold"}}
				]
			}
		}
	]`)}); err == nil {
		t.Errorf("wanted error for non-one link listing")
	}

	thread, err := parseThread(&rcloser{bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {"id": "harold"}}
				]
			}
		}
	]`)})
	if err != nil {
		t.Fatalf("error: %v", err)
	}
	if thread.GetTitle() != "hola" {
		t.Errorf("got %s; wanted hola", thread.GetTitle())
	}
	if len(thread.GetComments()) != 2 {
		t.Errorf("got %v; wanted 2 comments", thread.GetComments)
	}

	if _, err := parseThread(&rcloser{bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {
							"replies": "",
							"id": "harold"
						}
					}
				]
			}
		}
	]`)}); err != nil {
		t.Fatalf("error: %v", err)
	}
}

func TestParseInbox(t *testing.T) {
	if _, err := parseInbox(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseInbox(
		&rcloser{bytes.NewBufferString(`[]"`)},
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseInbox(
		&rcloser{bytes.NewBufferString(`{}`)},
	); err == nil {
		t.Errorf("wanted error for nil data")
	}

	if _, err := parseInbox(
		&rcloser{bytes.NewBufferString(`{"data": {}}`)},
	); err == nil {
		t.Errorf("wanted error for nil children")
	}

	messages, err := parseInbox(&rcloser{bytes.NewBufferString(`{
		"data": {
			"children" : [
				{"data": {"was_comment": true}}
			]
		}
	}`)})
	if err != nil {
		t.Errorf("error: %v", err)
	}

	if len(messages) != 1 {
		t.Fatalf("got %d messages; wanted 1", len(messages))
	}

	if !messages[0].GetWasComment() {
		t.Errorf("wanted message to be a comment")
	}
}

func TestUnpackCommentListing(t *testing.T) {
	if unpackCommentListing(&redditproto.CommentListing{}) != nil {
		t.Errorf("wanted empty slice when data is nil")
	}

	if unpackCommentListing(&redditproto.CommentListing{
		Data: &redditproto.CommentData{},
	}) != nil {
		t.Errorf("wanted empty slice when children is nil")
	}

	topId := "top"
	subId := "sub"
	sublisting := &redditproto.CommentListing{
		Data: &redditproto.CommentData{
			Children: []*redditproto.CommentChildren{
				&redditproto.CommentChildren{
					Data: &redditproto.Comment{
						Id: &subId,
					},
				},
			},
		},
	}
	listing := &redditproto.CommentListing{
		Data: &redditproto.CommentData{
			Children: []*redditproto.CommentChildren{
				&redditproto.CommentChildren{
					Data: &redditproto.Comment{
						Id:      &topId,
						Replies: sublisting,
					},
				},
			},
		},
	}
	expected := []*redditproto.Comment{
		&redditproto.Comment{
			Id: &topId,
			ReplyTree: []*redditproto.Comment{
				&redditproto.Comment{
					Id: &subId,
				},
			},
		},
	}
	actual := unpackCommentListing(listing)
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("got %v; wanted %v", actual, expected)
	}
}
<commit_msg>Use ioutil.NopCloser in place of rcloser.<commit_after>package operator

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/turnage/redditproto"
)

type errReader struct{}

func (e *errReader) Read(p []byte) (int, error) {
	return 0, fmt.Errorf("an error")
}

func (e *errReader) Close() error {
	return nil
}

func TestParseLinkListing(t *testing.T) {
	if _, err := parseLinkListing(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseLinkListing(
		ioutil.NopCloser(bytes.NewBufferString(`[]"`)),
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseLinkListing(
		ioutil.NopCloser(bytes.NewBufferString(`{}`)),
	); err == nil {
		t.Errorf("wanted error for nil data")
	}

	if _, err := parseLinkListing(
		ioutil.NopCloser(bytes.NewBufferString(`{"data": {}}`)),
	); err == nil {
		t.Errorf("wanted error for nil children")
	}

	links, err := parseLinkListing(ioutil.NopCloser(bytes.NewBufferString(`{
		"data": {
			"children": [
				{"data": {"title": "hello"}},
				{"data": {"title": "hola"}},
				{"data": {"title": "bye"}}
			]
		}
	}`)))
	if err != nil {
		t.Fatalf("error: %v", err)
	}
	if len(links) != 3 {
		t.Errorf("wanted to find 3 links; resp is %v", links)
	}
}

func TestParseThread(t *testing.T) {
	if _, err := parseThread(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseThread(&errReader{}); err == nil {
		t.Errorf("Wanted error for failed read")
	}

	if _, err := parseThread(
		ioutil.NopCloser(bytes.NewBufferString(`asds`)),
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseThread(
		ioutil.NopCloser(bytes.NewBufferString(`[]`)),
	); err == nil {
		t.Errorf("wanted error for missing listings")
	}

	if _, err := parseThread(ioutil.NopCloser(bytes.NewBufferString(`[
			{"data": {}},
			{"data": {}}
	]`))); err == nil {
		t.Errorf("wanted error for bad link listing")
	}

	if _, err := parseThread(ioutil.NopCloser(bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}},
					{"data": {"title": "bye"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {"id": "harold"}}
				]
			}
		}
	]`))); err == nil {
		t.Errorf("wanted error for non-one link listing")
	}

	thread, err := parseThread(ioutil.NopCloser(bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {"id": "harold"}}
				]
			}
		}
	]`)))
	if err != nil {
		t.Fatalf("error: %v", err)
	}
	if thread.GetTitle() != "hola" {
		t.Errorf("got %s; wanted hola", thread.GetTitle())
	}
	if len(thread.GetComments()) != 2 {
		t.Errorf("got %v; wanted 2 comments", thread.GetComments)
	}

	if _, err := parseThread(ioutil.NopCloser(bytes.NewBufferString(`[
		{
			"data": {
				"children": [
					{"data": {"title": "hola"}}
				]
			}
		},
		{
			"data": {
				"children": [
					{"data": {"id": "arnold"}},
					{"data": {
							"replies": "",
							"id": "harold"
						}
					}
				]
			}
		}
	]`))); err != nil {
		t.Fatalf("error: %v", err)
	}
}

func TestParseInbox(t *testing.T) {
	if _, err := parseInbox(nil); err == nil {
		t.Errorf("wanted error for nil content")
	}

	if _, err := parseInbox(
		ioutil.NopCloser(bytes.NewBufferString(`[]"`)),
	); err == nil {
		t.Errorf("wanted error for invalid json")
	}

	if _, err := parseInbox(
		ioutil.NopCloser(bytes.NewBufferString(`{}`)),
	); err == nil {
		t.Errorf("wanted error for nil data")
	}

	if _, err := parseInbox(
		ioutil.NopCloser(bytes.NewBufferString(`{"data": {}}`)),
	); err == nil {
		t.Errorf("wanted error for nil children")
	}

	messages, err := parseInbox(ioutil.NopCloser(bytes.NewBufferString(`{
		"data": {
			"children" : [
				{"data": {"was_comment": true}}
			]
		}
	}`)))
	if err != nil {
		t.Errorf("error: %v", err)
	}

	if len(messages) != 1 {
		t.Fatalf("got %d messages; wanted 1", len(messages))
	}

	if !messages[0].GetWasComment() {
		t.Errorf("wanted message to be a comment")
	}
}

func TestUnpackCommentListing(t *testing.T) {
	if unpackCommentListing(&redditproto.CommentListing{}) != nil {
		t.Errorf("wanted empty slice when data is nil")
	}

	if unpackCommentListing(&redditproto.CommentListing{
		Data: &redditproto.CommentData{},
	}) != nil {
		t.Errorf("wanted empty slice when children is nil")
	}

	topId := "top"
	subId := "sub"
	sublisting := &redditproto.CommentListing{
		Data: &redditproto.CommentData{
			Children: []*redditproto.CommentChildren{
				&redditproto.CommentChildren{
					Data: &redditproto.Comment{
						Id: &subId,
					},
				},
			},
		},
	}
	listing := &redditproto.CommentListing{
		Data: &redditproto.CommentData{
			Children: []*redditproto.CommentChildren{
				&redditproto.CommentChildren{
					Data: &redditproto.Comment{
						Id:      &topId,
						Replies: sublisting,
					},
				},
			},
		},
	}
	expected := []*redditproto.Comment{
		&redditproto.Comment{
			Id: &topId,
			ReplyTree: []*redditproto.Comment{
				&redditproto.Comment{
					Id: &subId,
				},
			},
		},
	}
	actual := unpackCommentListing(listing)
	if !reflect.DeepEqual(actual, expected) {
		t.Errorf("got %v; wanted %v", actual, expected)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package restore

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/dag"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func marshalFileOrDie(content []byte) (chunk []byte) {
	chunk, err := repr.MarshalFile(content)
	AssertEq(nil, err)
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type VisitorTest struct {
	ctx       context.Context
	blobStore blob.Store

	// A directory that is deleted when the test completes.
	dir string

	// A visitor configured with the above directory.
	visitor dag.Visitor
}

var _ SetUpInterface = &VisitorTest{}
var _ TearDownInterface = &VisitorTest{}

func init() { RegisterTestSuite(&VisitorTest{}) }

func (t *VisitorTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Create the blob store.
	t.blobStore, err = newFakeBlobStore(t.ctx)
	AssertEq(nil, err)

	// Set up the directory.
	t.dir, err = ioutil.TempDir("", "visitor_test")
	AssertEq(nil, err)

	// Create the visitor.
	t.visitor = newVisitor(t.dir, t.blobStore, log.New(ioutil.Discard, "", 0))
}

func (t *VisitorTest) TearDown() {
	var err error

	err = os.RemoveAll(t.dir)
	AssertEq(nil, err)
}

func (t *VisitorTest) call(n *node) (err error) {
	err = t.visitor.Visit(t.ctx, n)
	return
}

func (t *VisitorTest) store(b []byte) (s blob.Score, err error) {
	s, err = t.blobStore.Store(
		t.ctx,
		&blob.StoreRequest{Blob: b})

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *VisitorTest) File_MissingBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := marshalFileOrDie([]byte("burrito"))
	score1 := blob.ComputeScore(chunk1)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *VisitorTest) File_CorruptBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalFile")))
}

func (t *VisitorTest) File_Empty() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) File_NonEmpty() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1},
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	contents, err := ioutil.ReadFile(p)
	AssertEq(nil, err)
	ExpectEq("tacoburrito", string(contents))
}

func (t *VisitorTest) File_PermsAndModTime() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) Directory() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeDir, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))
}

func (t *VisitorTest) Directory_AlreadyExists() {
	var err error

	n := &node{
		RelPath: "foo",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "foo",
			Permissions: 0741,
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create the directory.
	err = os.Mkdir(p, 0700)
	AssertEq(nil, err)

	// The call should succeed and the permissions should be updated as usual.
	err = t.call(n)
	AssertEq(nil, err)

	fi, err := os.Lstat(p)
	AssertEq(nil, err)
	ExpectEq(0741|os.ModeDir, fi.Mode())
}

func (t *VisitorTest) Symlink() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
			Target:      "taco/burrito",
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeSymlink, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))

	// Readlink
	target, err := os.Readlink(p)

	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}

func (t *VisitorTest) ParentDirsAlreadyExist() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0700,
			Target:      "taco/burrito",
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create leading directories.
	err = os.MkdirAll(path.Dir(p), 0700)
	AssertEq(nil, err)

	// Call. Everything should work as usual.
	err = t.call(n)
	AssertEq(nil, err)

	target, err := os.Readlink(p)
	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}
<commit_msg>VisitorTest.File_Empty<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package restore

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/dag"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func marshalFileOrDie(content []byte) (chunk []byte) {
	chunk, err := repr.MarshalFile(content)
	AssertEq(nil, err)
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type VisitorTest struct {
	ctx       context.Context
	blobStore blob.Store

	// A directory that is deleted when the test completes.
	dir string

	// A visitor configured with the above directory.
	visitor dag.Visitor
}

var _ SetUpInterface = &VisitorTest{}
var _ TearDownInterface = &VisitorTest{}

func init() { RegisterTestSuite(&VisitorTest{}) }

func (t *VisitorTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Create the blob store.
	t.blobStore, err = newFakeBlobStore(t.ctx)
	AssertEq(nil, err)

	// Set up the directory.
	t.dir, err = ioutil.TempDir("", "visitor_test")
	AssertEq(nil, err)

	// Create the visitor.
	t.visitor = newVisitor(t.dir, t.blobStore, log.New(ioutil.Discard, "", 0))
}

func (t *VisitorTest) TearDown() {
	var err error

	err = os.RemoveAll(t.dir)
	AssertEq(nil, err)
}

func (t *VisitorTest) call(n *node) (err error) {
	err = t.visitor.Visit(t.ctx, n)
	return
}

func (t *VisitorTest) store(b []byte) (s blob.Score, err error) {
	s, err = t.blobStore.Store(
		t.ctx,
		&blob.StoreRequest{Blob: b})

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *VisitorTest) File_MissingBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := marshalFileOrDie([]byte("burrito"))
	score1 := blob.ComputeScore(chunk1)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *VisitorTest) File_CorruptBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalFile")))
}

func (t *VisitorTest) File_Empty() {
	var err error

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{},
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	contents, err := ioutil.ReadFile(p)
	AssertEq(nil, err)
	ExpectEq("", string(contents))
}

func (t *VisitorTest) File_NonEmpty() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1},
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	contents, err := ioutil.ReadFile(p)
	AssertEq(nil, err)
	ExpectEq("tacoburrito", string(contents))
}

func (t *VisitorTest) File_PermsAndModTime() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) Directory() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeDir, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))
}

func (t *VisitorTest) Directory_AlreadyExists() {
	var err error

	n := &node{
		RelPath: "foo",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "foo",
			Permissions: 0741,
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create the directory.
	err = os.Mkdir(p, 0700)
	AssertEq(nil, err)

	// The call should succeed and the permissions should be updated as usual.
	err = t.call(n)
	AssertEq(nil, err)

	fi, err := os.Lstat(p)
	AssertEq(nil, err)
	ExpectEq(0741|os.ModeDir, fi.Mode())
}

func (t *VisitorTest) Symlink() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
			Target:      "taco/burrito",
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeSymlink, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))

	// Readlink
	target, err := os.Readlink(p)

	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}

func (t *VisitorTest) ParentDirsAlreadyExist() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0700,
			Target:      "taco/burrito",
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create leading directories.
	err = os.MkdirAll(path.Dir(p), 0700)
	AssertEq(nil, err)

	// Call. Everything should work as usual.
	err = t.call(n)
	AssertEq(nil, err)

	target, err := os.Readlink(p)
	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}
<|endoftext|>