<commit_before>package walnut

import (
	"testing"
	"time"
)

var stringTests = []struct {
	in  string
	out string
	ok  bool
}{
	// valid
	{`""`, "", true},
	{`"hello"`, "hello", true},
	{`"日本人"`, "日本人", true},
	{`"a\nb"`, "a\nb", true},
	{`"\u00FF"`, "ÿ", true},
	{`"\xFF"`, "\xFF", true},
	{`"\U00010111"`, "\U00010111", true},
	{`"\U0001011111"`, "\U0001011111", true},
	{`"'"`, "'", true},
	{`"\""`, "\"", true},

	// invalid
	{``, "", false},
	{`"lone`, "", false},
	{`hello`, "", false},
	{`"mismatch'`, "", false},
	{`"\"`, "", false},
	{`"\1"`, "", false},
	{`"\19"`, "", false},
	{`"\129"`, "", false},
	{"`a`", "", false},
	{"'b'", "", false},
}

var intTests = []struct {
	in  string
	out int64
	ok  bool
}{
	// decimal
	{"0", 0, true},
	{"10", 10, true},
	{"123456789", 123456789, true},

	// hexadecimal
	{"0x02", 2, true},
	{"0xff", 255, true},
	{"0xc", 12, true},

	// octal
	{"010", 8, true},
	{"01234567", 342391, true},
	{"012345678", 0, false},

	// signs
	{"+0", 0, true},
	{"-0", 0, true},
	{"+10", 10, true},
	{"-0x00", 0, true},
	{"-0x10", -16, true},
	{"+01", 1, true},
	{"-010", -8, true},

	// limits
	{"9223372036854775807", 1<<63 - 1, true},
	{"9223372036854775808", 0, false},
	{"9223372036854775809", 0, false},
	{"-9223372036854775807", -(1<<63 - 1), true},
	{"-9223372036854775808", -1 << 63, true},
	{"-9223372036854775809", 0, false},

	{"0x7FFFFFFFFFFFFFFF", 1<<63 - 1, true},
	{"0X8000000000000000", 0, false},
	{"0X8000000000000001", 0, false},
	{"-0x7FFFFFFFFFFFFFFF", -(1<<63 - 1), true},
	{"-0X8000000000000000", -1 << 63, true},
	{"-0X8000000000000001", 0, false},

	{"0777777777777777777777", 1<<63 - 1, true},
	{"01000000000000000000000", 0, false},
	{"01000000000000000000001", 0, false},
	{"-0777777777777777777777", -(1<<63 - 1), true},
	{"-01000000000000000000000", -1 << 63, true},
	{"-01000000000000000000001", 0, false},

	// invalid
	{"", 0, false},
	{"abc", 0, false},
	{"100 blue", 0, false},
	{"-0-", 0, false},
	{"++0", 0, false},
}

var boolTests = []struct {
	in  string
	out bool
	ok  bool
}{
	// truthy
	{"true", true, true},
	{"yes", true, true},
	{"on", true, true},

	// falsy
	{"false", false, true},
	{"no", false, true},
	{"off", false, true},

	// invalid
	{"", false, false},
	{"y", false, false},
	{"foo", false, false},
}

var durationTests = []struct {
	in  string
	out time.Duration
	ok  bool
}{
	// simple formats
	{"0s", 0, true},
	{"5s", 5 * time.Second, true},
	{"37s", 37 * time.Second, true},
	{"010s", 10 * time.Second, true},
	{"3d", 3 * 24 * time.Hour, true},

	// all units
	{"10ns", 10 * time.Nanosecond, true},
	{"10µs", 10 * time.Microsecond, true},
	{"10μs", 10 * time.Microsecond, true},
	{"10us", 10 * time.Microsecond, true},
	{"10ms", 10 * time.Millisecond, true},
	{"10s", 10 * time.Second, true},
	{"10m", 10 * time.Minute, true},
	{"10h", 10 * time.Hour, true},
	{"10d", 10 * 24 * time.Hour, true},
	{"10w", 10 * 7 * 24 * time.Hour, true},

	// mixed units
	{"1h1m1s", time.Hour + time.Minute + time.Second, true},
	{"4h30m", 4*time.Hour + 30*time.Minute, true},
	{"1s500ms", time.Second + 500*time.Millisecond, true},
	{"1w1d24h1440m", 10 * 24 * time.Hour, true},

	// allow (ignore) spaces
	{"1h 1m1s", time.Hour + time.Minute + time.Second, true},
	{"4h 30m", 4*time.Hour + 30*time.Minute, true},
	{"1s    500ms", time.Second + 500*time.Millisecond, true},
	{"1w 1d 24h 1440m", 10 * 24 * time.Hour, true},

	// disallow signs and decimal values
	{"-3h", 0, false},
	{"+5m", 0, false},
	{"300.5h", 0, false},
	{"1h 1m 1.3s", 0, false},
	{"10w -3d", 0, false},
	{"1.2d20m", 0, false},

	// units out of order
	{"1s2m", 0, false},
	{"1200ms 3s", 0, false},
	{"4h 5d 6w 7m", 0, false},

	// other invalid formats
	{"", 0, false},
	{"1sm", 0, false},
	{"2 m 3 s", 0, false},
	{"4 d5 h", 0, false},
	{"100", 0, false},
	{"1d 200", 0, false},
	{"3 4 5ms", 0, false},
}

var timeTests = []struct {
	in  string
	out time.Time
	ok  bool
}{
	// basic
	{"1970-01-01 00:00:00 +0000", date(1970, 1, 1, 0, 0, 0, 0), true},
	{"2001-02-03 04:05:06 +0000", date(2001, 2, 3, 4, 5, 6, 0), true},
	{"1997-08-28 15:30:27.123 +0000", date(1997, 8, 28, 15, 30, 27, 123), true},
	{"1997-08-28 14:07:27 -0123", date(1997, 8, 28, 15, 30, 27, 0), true},

	// invalid
	{"1970-02-48 00:00:00 +0000", time.Time{}, false},
	{"70-01-01 00:00:00", time.Time{}, false},
	{"1970-01-01 00:00:00 UTC", time.Time{}, false},
}

func TestParseString(t *testing.T) {
	for _, test := range stringTests {
		fail := setup(t, "ParseString", test.in)
		out, ok := ParseString(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test.out)
		}
	}
}

func TestParseInt(t *testing.T) {
	for _, test := range intTests {
		fail := setup(t, "ParseInt", test.in)
		out, ok := ParseInt(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test.out)
		}
	}
}

func TestParseBool(t *testing.T) {
	for _, test := range boolTests {
		fail := setup(t, "ParseBool", test.in)
		out, ok := ParseBool(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test)
		}
	}
}

func TestParseDuration(t *testing.T) {
	for _, test := range durationTests {
		fail := setup(t, "ParseDuration", test.in)
		out, ok := ParseDuration(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%s != %s", out.String(), test.out.String())
		}
	}
}

func TestParseTime(t *testing.T) {
	for _, test := range timeTests {
		fail := setup(t, "ParseTime", test.in)
		out, ok := ParseTime(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if !out.Equal(test.out) {
			fail("%s != %s", out.String(), test.out.String())
		}
	}
}

// Simplify failure reporting.
type failFunc func(format string, values ...interface{})

func setup(t *testing.T, signature string, input interface{}) failFunc {
	return func(format string, values ...interface{}) {
		args := append([]interface{}{signature, input}, values...)
		t.Errorf("%s(%#v): "+format+"\n", args...)
	}
}

// Reduced version of `time.Date`.
func date(y, m, d, H, M, S, ms int) time.Time {
	month := time.Month(m)
	nano := ms * int(time.Millisecond)

	return time.Date(y, month, d, H, M, S, nano, time.UTC)
}
<commit_msg>Add type detection tests<commit_after>package walnut

import (
	"testing"
	"time"
)

var stringTests = []struct {
	in  string
	out string
	ok  bool
}{
	// valid
	{`""`, "", true},
	{`"hello"`, "hello", true},
	{`"日本人"`, "日本人", true},
	{`"a\nb"`, "a\nb", true},
	{`"\u00FF"`, "ÿ", true},
	{`"\xFF"`, "\xFF", true},
	{`"\U00010111"`, "\U00010111", true},
	{`"\U0001011111"`, "\U0001011111", true},
	{`"'"`, "'", true},
	{`"\""`, "\"", true},

	// invalid
	{``, "", false},
	{`"lone`, "", false},
	{`hello`, "", false},
	{`"mismatch'`, "", false},
	{`"\"`, "", false},
	{`"\1"`, "", false},
	{`"\19"`, "", false},
	{`"\129"`, "", false},
	{"`a`", "", false},
	{"'b'", "", false},
}

var intTests = []struct {
	in  string
	out int64
	ok  bool
}{
	// decimal
	{"0", 0, true},
	{"10", 10, true},
	{"123456789", 123456789, true},

	// hexadecimal
	{"0x02", 2, true},
	{"0xff", 255, true},
	{"0xc", 12, true},

	// octal
	{"010", 8, true},
	{"01234567", 342391, true},
	{"012345678", 0, false},

	// signs
	{"+0", 0, true},
	{"-0", 0, true},
	{"+10", 10, true},
	{"-0x00", 0, true},
	{"-0x10", -16, true},
	{"+01", 1, true},
	{"-010", -8, true},

	// limits
	{"9223372036854775807", 1<<63 - 1, true},
	{"9223372036854775808", 0, false},
	{"9223372036854775809", 0, false},
	{"-9223372036854775807", -(1<<63 - 1), true},
	{"-9223372036854775808", -1 << 63, true},
	{"-9223372036854775809", 0, false},

	{"0x7FFFFFFFFFFFFFFF", 1<<63 - 1, true},
	{"0X8000000000000000", 0, false},
	{"0X8000000000000001", 0, false},
	{"-0x7FFFFFFFFFFFFFFF", -(1<<63 - 1), true},
	{"-0X8000000000000000", -1 << 63, true},
	{"-0X8000000000000001", 0, false},

	{"0777777777777777777777", 1<<63 - 1, true},
	{"01000000000000000000000", 0, false},
	{"01000000000000000000001", 0, false},
	{"-0777777777777777777777", -(1<<63 - 1), true},
	{"-01000000000000000000000", -1 << 63, true},
	{"-01000000000000000000001", 0, false},

	// invalid
	{"", 0, false},
	{"abc", 0, false},
	{"100 blue", 0, false},
	{"-0-", 0, false},
	{"++0", 0, false},
}

var boolTests = []struct {
	in  string
	out bool
	ok  bool
}{
	// truthy
	{"true", true, true},
	{"yes", true, true},
	{"on", true, true},

	// falsy
	{"false", false, true},
	{"no", false, true},
	{"off", false, true},

	// invalid
	{"", false, false},
	{"y", false, false},
	{"foo", false, false},
}

var durationTests = []struct {
	in  string
	out time.Duration
	ok  bool
}{
	// simple formats
	{"0s", 0, true},
	{"5s", 5 * time.Second, true},
	{"37s", 37 * time.Second, true},
	{"010s", 10 * time.Second, true},
	{"3d", 3 * 24 * time.Hour, true},

	// all units
	{"10ns", 10 * time.Nanosecond, true},
	{"10µs", 10 * time.Microsecond, true},
	{"10μs", 10 * time.Microsecond, true},
	{"10us", 10 * time.Microsecond, true},
	{"10ms", 10 * time.Millisecond, true},
	{"10s", 10 * time.Second, true},
	{"10m", 10 * time.Minute, true},
	{"10h", 10 * time.Hour, true},
	{"10d", 10 * 24 * time.Hour, true},
	{"10w", 10 * 7 * 24 * time.Hour, true},

	// mixed units
	{"1h1m1s", time.Hour + time.Minute + time.Second, true},
	{"4h30m", 4*time.Hour + 30*time.Minute, true},
	{"1s500ms", time.Second + 500*time.Millisecond, true},
	{"1w1d24h1440m", 10 * 24 * time.Hour, true},

	// allow (ignore) spaces
	{"1h 1m1s", time.Hour + time.Minute + time.Second, true},
	{"4h 30m", 4*time.Hour + 30*time.Minute, true},
	{"1s    500ms", time.Second + 500*time.Millisecond, true},
	{"1w 1d 24h 1440m", 10 * 24 * time.Hour, true},

	// disallow signs and decimal values
	{"-3h", 0, false},
	{"+5m", 0, false},
	{"300.5h", 0, false},
	{"1h 1m 1.3s", 0, false},
	{"10w -3d", 0, false},
	{"1.2d20m", 0, false},

	// units out of order
	{"1s2m", 0, false},
	{"1200ms 3s", 0, false},
	{"4h 5d 6w 7m", 0, false},

	// other invalid formats
	{"", 0, false},
	{"1sm", 0, false},
	{"2 m 3 s", 0, false},
	{"4 d5 h", 0, false},
	{"100", 0, false},
	{"1d 200", 0, false},
	{"3 4 5ms", 0, false},
}

var timeTests = []struct {
	in  string
	out time.Time
	ok  bool
}{
	// basic
	{"1970-01-01 00:00:00 +0000", date(1970, 1, 1, 0, 0, 0, 0), true},
	{"2001-02-03 04:05:06 +0000", date(2001, 2, 3, 4, 5, 6, 0), true},
	{"1997-08-28 15:30:27.123 +0000", date(1997, 8, 28, 15, 30, 27, 123), true},
	{"1997-08-28 14:07:27 -0123", date(1997, 8, 28, 15, 30, 27, 0), true},

	// invalid
	{"1970-02-48 00:00:00 +0000", time.Time{}, false},
	{"70-01-01 00:00:00", time.Time{}, false},
	{"1970-01-01 00:00:00 UTC", time.Time{}, false},
}

func TestParseString(t *testing.T) {
	for _, test := range stringTests {
		fail := setup(t, "ParseString", test.in)
		out, ok := ParseString(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test.out)
		}
	}
}

func TestParseInt(t *testing.T) {
	for _, test := range intTests {
		fail := setup(t, "ParseInt", test.in)
		out, ok := ParseInt(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test.out)
		}
	}
}

func TestParseBool(t *testing.T) {
	for _, test := range boolTests {
		fail := setup(t, "ParseBool", test.in)
		out, ok := ParseBool(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%#v != %#v", out, test)
		}
	}
}

func TestParseDuration(t *testing.T) {
	for _, test := range durationTests {
		fail := setup(t, "ParseDuration", test.in)
		out, ok := ParseDuration(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if out != test.out {
			fail("%s != %s", out.String(), test.out.String())
		}
	}
}

func TestParseTime(t *testing.T) {
	for _, test := range timeTests {
		fail := setup(t, "ParseTime", test.in)
		out, ok := ParseTime(test.in)

		if ok != test.ok {
			if test.ok {
				fail("parsing failed unexpectedly")
			} else {
				fail("parsing should not succeed")
			}
			continue
		}

		if !out.Equal(test.out) {
			fail("%s != %s", out.String(), test.out.String())
		}
	}
}

type typeTest struct {
	in    string
	match bool
	kind  int
}

func TestDetectType(t *testing.T) {
	tests := make([]typeTest, 0)

	// create a set of type detection tests using all
	// tests for value parsing
	for _, d := range stringTests {
		tests = append(tests, typeTest{d.in, d.ok, TypeString})
	}

	for _, d := range intTests {
		tests = append(tests, typeTest{d.in, d.ok, TypeInt})
	}

	for _, d := range boolTests {
		tests = append(tests, typeTest{d.in, d.ok, TypeBool})
	}

	for _, d := range durationTests {
		tests = append(tests, typeTest{d.in, d.ok, TypeDuration})
	}

	for _, d := range timeTests {
		tests = append(tests, typeTest{d.in, d.ok, TypeTime})
	}

	// iterate through
	for _, test := range tests {
		fail := setup(t, "DetectType", test.in)
		d := DetectType(test.in)

		if test.match && d != test.kind {
			fail("%v != %v", d, test.kind)
		}
		if !test.match && d == test.kind {
			fail("%v == %v", d, test.kind)
		}
	}
}

// Simplify failure reporting.
type failFunc func(format string, values ...interface{})

func setup(t *testing.T, signature string, input interface{}) failFunc {
	return func(format string, values ...interface{}) {
		args := append([]interface{}{signature, input}, values...)
		t.Errorf("%s(%#v): "+format+"\n", args...)
	}
}

// Reduced version of `time.Date`.
func date(y, m, d, H, M, S, ms int) time.Time {
	month := time.Month(m)
	nano := ms * int(time.Millisecond)

	return time.Date(y, month, d, H, M, S, nano, time.UTC)
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/kr/pretty"
)

func TestParseAST(t *testing.T) {
	defaultPos = Pos{}
	for i, c := range astTests {
		want := fullProg(c.ast)
		setPosRecurse(t, want.Stmts, defaultPos, false)
		for j, in := range c.strs {
			t.Run(fmt.Sprintf("%d-%d", i, j), singleParseAST(in, want))
		}
	}
}

func singleParseAST(in string, want File) func(t *testing.T) {
	return func(t *testing.T) {
		r := strings.NewReader(in)
		got, err := Parse(r, "")
		if err != nil {
			t.Fatalf("Unexpected error in %q: %v", in, err)
		}
		setPosRecurse(t, got.Stmts, defaultPos, true)
		if !reflect.DeepEqual(got, want) {
			t.Fatalf("AST mismatch in %q\ndiff:\n%s", in,
				strings.Join(pretty.Diff(want, got), "\n"),
			)
		}
	}
}

var errBadReader = fmt.Errorf("read: expected error")

type badReader struct{}

func (b badReader) Read(p []byte) (int, error) { return 0, errBadReader }

func TestReadErr(t *testing.T) {
	var in badReader
	_, err := Parse(in, "")
	if err == nil {
		t.Fatalf("Expected error with bad reader")
	}
	if err != errBadReader {
		t.Fatalf("Error mismatch with bad reader:\nwant: %v\ngot:  %v",
			errBadReader, err)
	}
}

var errTests = []struct {
	in, want string
}{
	{
		"'",
		`1:1: reached EOF without closing quote '`,
	},
	{
		`"`,
		`1:1: reached EOF without closing quote "`,
	},
	{
		`'\''`,
		`1:4: reached EOF without closing quote '`,
	},
	{
		";",
		`1:1: ; can only immediately follow a statement`,
	},
	{
		"{ ; }",
		`1:3: ; can only immediately follow a statement`,
	},
	{
		"à(){}",
		`1:1: invalid func name: à`,
	},
	{
		`"foo"(){}`,
		`1:1: invalid func name: "foo"`,
	},
	{
		`'foo'(){}`,
		`1:1: invalid func name: 'foo'`,
	},
	{
		"{",
		`1:1: reached EOF without matching token { with }`,
	},
	{
		"}",
		`1:1: } can only be used to close a block`,
	},
	{
		"{ #}",
		`1:1: reached EOF without matching token { with }`,
	},
	{
		"(",
		`1:1: reached EOF without matching token ( with )`,
	},
	{
		")",
		`1:1: ) can only be used to close a subshell`,
	},
	{
		"`",
		"1:1: reached EOF without closing quote `",
	},
	{
		";;",
		`1:1: ;; is not a valid start for a statement`,
	},
	{
		"( foo;",
		`1:1: reached EOF without matching token ( with )`,
	},
	{
		"&",
		`1:1: & can only immediately follow a statement`,
	},
	{
		"|",
		`1:1: | can only immediately follow a statement`,
	},
	{
		"&&",
		`1:1: && can only immediately follow a statement`,
	},
	{
		"||",
		`1:1: || can only immediately follow a statement`,
	},
	{
		"foo; || bar",
		`1:6: || can only immediately follow a statement`,
	},
	{
		"foo & || bar",
		`1:7: || can only immediately follow a statement`,
	},
	{
		"foo;;",
		`1:4: a command can only contain words and redirects`,
	},
	{
		"foo(",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"foo(bar",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"à(",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"function",
		`1:1: "function" must be followed by a word`,
	},
	{
		"function foo(",
		`1:10: "foo(" must be followed by ")"`,
	},
	{
		"foo'",
		`1:4: reached EOF without closing quote '`,
	},
	{
		`foo"`,
		`1:4: reached EOF without closing quote "`,
	},
	{
		"foo()",
		`1:1: "foo()" must be followed by a statement`,
	},
	{
		"function foo()",
		`1:1: "foo()" must be followed by a statement`,
	},
	{
		"foo() {",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo foo(",
		`1:9: a command can only contain words and redirects`,
	},
	{
		"foo &&",
		`1:5: && must be followed by a statement`,
	},
	{
		"foo |",
		`1:5: | must be followed by a statement`,
	},
	{
		"foo |&",
		`1:5: |& must be followed by a statement`,
	},
	{
		"foo ||",
		`1:5: || must be followed by a statement`,
	},
	{
		"foo >",
		`1:5: > must be followed by a word`,
	},
	{
		"foo >>",
		`1:5: >> must be followed by a word`,
	},
	{
		"foo <",
		`1:5: < must be followed by a word`,
	},
	{
		"foo <>",
		`1:5: <> must be followed by a word`,
	},
	{
		"foo <<<",
		`1:5: <<< must be followed by a word`,
	},
	{
		"if",
		`1:1: "if" must be followed by a statement list`,
	},
	{
		"if foo;",
		`1:1: "if [stmts]" must be followed by "then"`,
	},
	{
		"if foo then",
		`1:1: "if [stmts]" must be followed by "then"`,
	},
	{
		"if foo; then bar;",
		`1:1: if statement must end with "fi"`,
	},
	{
		"if a; then b; elif c;",
		`1:15: "elif [stmts]" must be followed by "then"`,
	},
	{
		"'foo' '",
		`1:7: reached EOF without closing quote '`,
	},
	{
		"'foo\n' '",
		`2:3: reached EOF without closing quote '`,
	},
	{
		"while",
		`1:1: "while" must be followed by a statement list`,
	},
	{
		"while foo;",
		`1:1: "while [stmts]" must be followed by "do"`,
	},
	{
		"while foo; do bar",
		`1:1: while statement must end with "done"`,
	},
	{
		"while foo; do bar;",
		`1:1: while statement must end with "done"`,
	},
	{
		"until",
		`1:1: "until" must be followed by a statement list`,
	},
	{
		"until foo;",
		`1:1: "until [stmts]" must be followed by "do"`,
	},
	{
		"until foo; do bar",
		`1:1: until statement must end with "done"`,
	},
	{
		"until foo; do bar;",
		`1:1: until statement must end with "done"`,
	},
	{
		"for",
		`1:1: "for" must be followed by a literal`,
	},
	{
		"for i",
		`1:1: "for foo" must be followed by "in", ; or a newline`,
	},
	{
		"for i in;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for i in 1 2 3;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for i in 1 2 &",
		`1:14: word list can only contain words`,
	},
	{
		"for i in 1 2 3; do echo $i;",
		`1:1: for statement must end with "done"`,
	},
	{
		"for i in 1 2 3; echo $i;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for 'i' in 1 2 3; do echo $i; done",
		`1:1: "for" must be followed by a literal`,
	},
	{
		"for in 1 2 3; do echo $i; done",
		`1:1: "for foo" must be followed by "in", ; or a newline`,
	},
	{
		"foo &\n;",
		`2:1: ; can only immediately follow a statement`,
	},
	{
		"echo $(foo",
		`1:6: reached EOF without matching token ( with )`,
	},
	{
		"echo $((foo",
		`1:6: reached EOF without matching token (( with ))`,
	},
	{
		"echo $((()))",
		`1:9: parentheses must enclose an expression`,
	},
	{
		"echo $(((3))",
		`1:6: reached ) without matching token (( with ))`,
	},
	{
		"echo $((+))",
		`1:9: + must be followed by an expression`,
	},
	{
		"echo $((a b c))",
		`1:11: not a valid arithmetic operator: literal`,
	},
	{
		"echo $((a *))",
		`1:11: * must be followed by an expression`,
	},
	{
		"echo $((++))",
		`1:9: ++ must be followed by a word`,
	},
	{
		"echo ${foo",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo $'",
		`1:6: reached EOF without closing quote '`,
	},
	{
		"echo ${}",
		`1:6: parameter expansion requires a literal`,
	},
	{
		"echo ${foo-bar",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo ${#foo-bar}",
		`1:12: can only get length of a simple parameter`,
	},
	{
		"#foo\n{",
		`2:1: reached EOF without matching token { with }`,
	},
	{
		`echo "foo${bar"`,
		`1:11: reached EOF without matching token { with }`,
	},
	{
		"foo\n;",
		`2:1: ; can only immediately follow a statement`,
	},
	{
		"(foo) bar",
		`1:7: statements must be separated by &, ; or a newline`,
	},
	{
		"{ foo; } bar",
		`1:10: statements must be separated by &, ; or a newline`,
	},
	{
		"if foo; then bar; fi bar",
		`1:22: statements must be separated by &, ; or a newline`,
	},
	{
		"case",
		`1:1: "case" must be followed by a word`,
	},
	{
		"case i",
		`1:1: "case x" must be followed by "in"`,
	},
	{
		"case i in 3) foo;",
		`1:1: case statement must end with "esac"`,
	},
	{
		"case i in 3) foo; 4) bar; esac",
		`1:20: a command can only contain words and redirects`,
	},
	{
		"case i in 3&) foo;",
		`1:12: case patterns must be separated with |`,
	},
	{
		"case i in &) foo;",
		`1:11: case patterns must consist of words`,
	},
	{
		"case i\r\nin\r\n3) foo;\r\ndone",
		`1:1: "case x" must be followed by "in"`,
	},
	{
		"\"`\"",
		`1:3: reached EOF without closing quote "`,
	},
	{
		"`\"`",
		"1:3: reached EOF without closing quote `",
	},
	{
		"`{\n`",
		"2:1: ` is not a valid start for a statement",
	},
	{
		"echo \"`)`\"",
		`1:8: ) can only be used to close a subshell`,
	},
	{
		"declare (",
		`1:9: "declare" must be followed by words`,
	},
	{
		"let a+ b",
		`1:6: + must be followed by an expression`,
	},
	{
		"let + a",
		`1:5: + must be followed by an expression`,
	},
	{
		"let a ++",
		`1:7: ++ must be followed by a word`,
	},
	{
		"let ))",
		`1:5: "let" must be followed by arithmetic expressions`,
	},
}

func TestParseErr(t *testing.T) {
	for i, c := range errTests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			r := strings.NewReader(c.in)
			_, err := Parse(r, "")
			if err == nil {
				t.Fatalf("Expected error in %q: %v", c.in, c.want)
			}
			got := err.Error()
			if got != c.want {
				t.Fatalf("Error mismatch in %q\nwant: %s\ngot:  %s",
					c.in, c.want, got)
			}
		})
	}
}

func TestInputName(t *testing.T) {
	in := errTests[0].in
	want := "some-file.sh:" + errTests[0].want
	r := strings.NewReader(in)
	_, err := Parse(r, "some-file.sh")
	if err == nil {
		t.Fatalf("Expected error in %q: %v", in, want)
	}
	got := err.Error()
	if got != want {
		t.Fatalf("Error mismatch in %q\nwant: %s\ngot:  %s",
			in, want, got)
	}
}
<commit_msg>print_test: better test names<commit_after>// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package sh

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/kr/pretty"
)

func TestParse(t *testing.T) {
	defaultPos = Pos{}
	for i, c := range astTests {
		want := fullProg(c.ast)
		setPosRecurse(t, want.Stmts, defaultPos, false)
		for j, in := range c.strs {
			t.Run(fmt.Sprintf("%d-%d", i, j), singleParse(in, want))
		}
	}
}

func singleParse(in string, want File) func(t *testing.T) {
	return func(t *testing.T) {
		r := strings.NewReader(in)
		got, err := Parse(r, "")
		if err != nil {
			t.Fatalf("Unexpected error in %q: %v", in, err)
		}
		setPosRecurse(t, got.Stmts, defaultPos, true)
		if !reflect.DeepEqual(got, want) {
			t.Fatalf("AST mismatch in %q\ndiff:\n%s", in,
				strings.Join(pretty.Diff(want, got), "\n"),
			)
		}
	}
}

var errBadReader = fmt.Errorf("read: expected error")

type badReader struct{}

func (b badReader) Read(p []byte) (int, error) { return 0, errBadReader }

func TestReadErr(t *testing.T) {
	var in badReader
	_, err := Parse(in, "")
	if err == nil {
		t.Fatalf("Expected error with bad reader")
	}
	if err != errBadReader {
		t.Fatalf("Error mismatch with bad reader:\nwant: %v\ngot:  %v",
			errBadReader, err)
	}
}

var errTests = []struct {
	in, want string
}{
	{
		"'",
		`1:1: reached EOF without closing quote '`,
	},
	{
		`"`,
		`1:1: reached EOF without closing quote "`,
	},
	{
		`'\''`,
		`1:4: reached EOF without closing quote '`,
	},
	{
		";",
		`1:1: ; can only immediately follow a statement`,
	},
	{
		"{ ; }",
		`1:3: ; can only immediately follow a statement`,
	},
	{
		"à(){}",
		`1:1: invalid func name: à`,
	},
	{
		`"foo"(){}`,
		`1:1: invalid func name: "foo"`,
	},
	{
		`'foo'(){}`,
		`1:1: invalid func name: 'foo'`,
	},
	{
		"{",
		`1:1: reached EOF without matching token { with }`,
	},
	{
		"}",
		`1:1: } can only be used to close a block`,
	},
	{
		"{ #}",
		`1:1: reached EOF without matching token { with }`,
	},
	{
		"(",
		`1:1: reached EOF without matching token ( with )`,
	},
	{
		")",
		`1:1: ) can only be used to close a subshell`,
	},
	{
		"`",
		"1:1: reached EOF without closing quote `",
	},
	{
		";;",
		`1:1: ;; is not a valid start for a statement`,
	},
	{
		"( foo;",
		`1:1: reached EOF without matching token ( with )`,
	},
	{
		"&",
		`1:1: & can only immediately follow a statement`,
	},
	{
		"|",
		`1:1: | can only immediately follow a statement`,
	},
	{
		"&&",
		`1:1: && can only immediately follow a statement`,
	},
	{
		"||",
		`1:1: || can only immediately follow a statement`,
	},
	{
		"foo; || bar",
		`1:6: || can only immediately follow a statement`,
	},
	{
		"foo & || bar",
		`1:7: || can only immediately follow a statement`,
	},
	{
		"foo;;",
		`1:4: a command can only contain words and redirects`,
	},
	{
		"foo(",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"foo(bar",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"à(",
		`1:1: "foo(" must be followed by ")"`,
	},
	{
		"function",
		`1:1: "function" must be followed by a word`,
	},
	{
		"function foo(",
		`1:10: "foo(" must be followed by ")"`,
	},
	{
		"foo'",
		`1:4: reached EOF without closing quote '`,
	},
	{
		`foo"`,
		`1:4: reached EOF without closing quote "`,
	},
	{
		"foo()",
		`1:1: "foo()" must be followed by a statement`,
	},
	{
		"function foo()",
		`1:1: "foo()" must be followed by a statement`,
	},
	{
		"foo() {",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo foo(",
		`1:9: a command can only contain words and redirects`,
	},
	{
		"foo &&",
		`1:5: && must be followed by a statement`,
	},
	{
		"foo |",
		`1:5: | must be followed by a statement`,
	},
	{
		"foo |&",
		`1:5: |& must be followed by a statement`,
	},
	{
		"foo ||",
		`1:5: || must be followed by a statement`,
	},
	{
		"foo >",
		`1:5: > must be followed by a word`,
	},
	{
		"foo >>",
		`1:5: >> must be followed by a word`,
	},
	{
		"foo <",
		`1:5: < must be followed by a word`,
	},
	{
		"foo <>",
		`1:5: <> must be followed by a word`,
	},
	{
		"foo <<<",
		`1:5: <<< must be followed by a word`,
	},
	{
		"if",
		`1:1: "if" must be followed by a statement list`,
	},
	{
		"if foo;",
		`1:1: "if [stmts]" must be followed by "then"`,
	},
	{
		"if foo then",
		`1:1: "if [stmts]" must be followed by "then"`,
	},
	{
		"if foo; then bar;",
		`1:1: if statement must end with "fi"`,
	},
	{
		"if a; then b; elif c;",
		`1:15: "elif [stmts]" must be followed by "then"`,
	},
	{
		"'foo' '",
		`1:7: reached EOF without closing quote '`,
	},
	{
		"'foo\n' '",
		`2:3: reached EOF without closing quote '`,
	},
	{
		"while",
		`1:1: "while" must be followed by a statement list`,
	},
	{
		"while foo;",
		`1:1: "while [stmts]" must be followed by "do"`,
	},
	{
		"while foo; do bar",
		`1:1: while statement must end with "done"`,
	},
	{
		"while foo; do bar;",
		`1:1: while statement must end with "done"`,
	},
	{
		"until",
		`1:1: "until" must be followed by a statement list`,
	},
	{
		"until foo;",
		`1:1: "until [stmts]" must be followed by "do"`,
	},
	{
		"until foo; do bar",
		`1:1: until statement must end with "done"`,
	},
	{
		"until foo; do bar;",
		`1:1: until statement must end with "done"`,
	},
	{
		"for",
		`1:1: "for" must be followed by a literal`,
	},
	{
		"for i",
		`1:1: "for foo" must be followed by "in", ; or a newline`,
	},
	{
		"for i in;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for i in 1 2 3;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for i in 1 2 &",
		`1:14: word list can only contain words`,
	},
	{
		"for i in 1 2 3; do echo $i;",
		`1:1: for statement must end with "done"`,
	},
	{
		"for i in 1 2 3; echo $i;",
		`1:1: "for foo [in words]" must be followed by "do"`,
	},
	{
		"for 'i' in 1 2 3; do echo $i; done",
		`1:1: "for" must be followed by a literal`,
	},
	{
		"for in 1 2 3; do echo $i; done",
		`1:1: "for foo" must be followed by "in", ; or a newline`,
	},
	{
		"foo &\n;",
		`2:1: ; can only immediately follow a statement`,
	},
	{
		"echo $(foo",
		`1:6: reached EOF without matching token ( with )`,
	},
	{
		"echo $((foo",
		`1:6: reached EOF without matching token (( with ))`,
	},
	{
		"echo $((()))",
		`1:9: parentheses must enclose an expression`,
	},
	{
		"echo $(((3))",
		`1:6: reached ) without matching token (( with ))`,
	},
	{
		"echo $((+))",
		`1:9: + must be followed by an expression`,
	},
	{
		"echo $((a b c))",
		`1:11: not a valid arithmetic operator: literal`,
	},
	{
		"echo $((a *))",
		`1:11: * must be followed by an expression`,
	},
	{
		"echo $((++))",
		`1:9: ++ must be followed by a word`,
	},
	{
		"echo ${foo",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo $'",
		`1:6: reached EOF without closing quote '`,
	},
	{
		"echo ${}",
		`1:6: parameter expansion requires a literal`,
	},
	{
		"echo ${foo-bar",
		`1:7: reached EOF without matching token { with }`,
	},
	{
		"echo ${#foo-bar}",
		`1:12: can only get length of a simple parameter`,
	},
	{
		"#foo\n{",
		`2:1: reached EOF without matching token { with }`,
	},
	{
		`echo "foo${bar"`,
		`1:11: reached EOF without matching token { with }`,
	},
	{
		"foo\n;",
		`2:1: ; can only immediately follow a statement`,
	},
	{
		"(foo) bar",
		`1:7: statements must be separated by &, ; or a newline`,
	},
	{
		"{ foo; } bar",
		`1:10: statements must be separated by &, ; or a newline`,
	},
	{
		"if foo; then bar; fi bar",
		`1:22: statements must be separated by &, ; or a newline`,
	},
	{
		"case",
		`1:1: "case" must be followed by a word`,
	},
	{
		"case i",
		`1:1: "case x" must be followed by "in"`,
	},
	{
		"case i in 3) foo;",
		`1:1: case statement must end with "esac"`,
	},
	{
		"case i in 3) foo; 4) bar; esac",
		`1:20: a command can only contain words and redirects`,
	},
	{
		"case i in 3&) foo;",
		`1:12: case patterns must be separated with |`,
	},
	{
		"case i in &) foo;",
		`1:11: case patterns must consist of words`,
	},
	{
		"case i\r\nin\r\n3) foo;\r\ndone",
		`1:1: "case x" must be followed by "in"`,
	},
	{
		"\"`\"",
		`1:3: reached EOF without closing quote "`,
	},
	{
		"`\"`",
		"1:3: reached EOF without closing quote `",
	},
	{
		"`{\n`",
		"2:1: ` is not a valid start for a statement",
	},
	{
		"echo \"`)`\"",
		`1:8: ) can only be used to close a subshell`,
	},
	{
		"declare (",
		`1:9: "declare" must be followed by words`,
	},
	{
		"let a+ b",
		`1:6: + must be followed by an expression`,
	},
	{
		"let + a",
		`1:5: + must be followed by an expression`,
	},
	{
		"let a ++",
		`1:7: ++ must be followed by a word`,
	},
	{
		"let ))",
		`1:5: "let" must be followed by arithmetic expressions`,
	},
}

func TestParseErr(t *testing.T) {
	for i, c := range errTests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			r := strings.NewReader(c.in)
			_, err := Parse(r, "")
			if err == nil {
				t.Fatalf("Expected error in %q: %v", c.in, c.want)
			}
			got := err.Error()
			if got != c.want {
				t.Fatalf("Error mismatch in %q\nwant: %s\ngot:  %s",
					c.in, c.want, got)
			}
		})
	}
}

func TestInputName(t *testing.T) {
	in := errTests[0].in
	want := "some-file.sh:" + errTests[0].want
	r := strings.NewReader(in)
	_, err := Parse(r, "some-file.sh")
	if err == nil {
		t.Fatalf("Expected error in %q: %v", in, want)
	}
	got := err.Error()
	if got != want {
		t.Fatalf("Error mismatch in %q\nwant: %s\ngot:  %s",
			in, want, got)
	}
}
<|endoftext|>


<commit_before>package narcissus

import (
	"testing"

	"honnef.co/go/augeas"
)

type foo struct {
	augeasPath string
	A          string `path:"a"`
}

type bar struct{}

func TestParseNotAPtr(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(foo{
		augeasPath: "/files/some/path",
	})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "invalid interface: not a ptr" {
		t.Errorf("Expected error not a ptr, got %s", err.Error())
	}
}

func TestParseNotAStruct(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	f := "foo"
	err = n.Parse(&f)

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "invalid interface: not a struct" {
		t.Errorf("Expected error not a struct, got %s", err.Error())
	}
}

func TestParseFieldNotFound(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{
		augeasPath: "/files/some/path",
	})

	t.Skip("Fix this")

	if err == nil {
		t.Error("Expected an error, got nothing")
	}
}

func TestNoAugeasPathValue(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "undefined path: no augeasPath value and no default" {
		t.Errorf("Expected error no augeasPath value and no default, got %s", err.Error())
	}
}

func TestNoAugeasPathField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&bar{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "undefined path: no augeasPath field" {
		t.Errorf("Expected error no augeasPath field, got %s", err.Error())
	}
}

type simpleValues struct {
	augeasPath string
	Str        string `path:"str"`
	Int        int    `path:"int"`
	Bool       bool   `path:"bool"`
}

func TestGetSimpleField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/str", "foo")
	n.Augeas.Set("/test/int", "42")
	n.Augeas.Set("/test/bool", "true")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if s.Str != "foo" {
		t.Errorf("Expected foo, got %s", s.Str)
	}

	if s.Int != 42 {
		t.Errorf("Expected 42, got %v", s.Int)
	}

	if s.Bool != true {
		t.Errorf("Expected true, got %v", s.Bool)
	}
}

func TestGetSimpleFieldWrongTypes(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/int", "a")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Int: failed to convert a to int: strconv.ParseInt: parsing \"a\": invalid syntax" {
		t.Errorf("Expected int conversion error, got %v", err)
	}

	n.Augeas.Remove("/test/int")
	n.Augeas.Set("/test/bool", "a")
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Bool: failed to convert a to bool: strconv.ParseBool: parsing \"a\": invalid syntax" {
		t.Errorf("Expected bool conversion error, got %v", err)
	}
}

type sliceValues struct {
	augeasPath string
	SlStr      []string `path:"slstr"`
	SlInt      []int    `path:"slint"`
	SlBool     []bool   `path:"slbool"`
	SlStrSeq   []string `type:"seq"`
}

func TestGetSliceField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	n.Augeas.Set("/test/1", "foo")
	n.Augeas.Set("/test/2", "bar")
	s := &sliceValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(s.SlStr) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStr))
	}
	if s.SlStr[1] != "b" {
		t.Errorf("Expected element to be b, got %s", s.SlStr[1])
	}

	if len(s.SlInt) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlInt))
	}
	if s.SlInt[1] != 2 {
		t.Errorf("Expected element to be 2, got %v", s.SlInt[1])
	}

	if len(s.SlBool) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlBool))
	}
	if s.SlBool[0] != true {
		t.Errorf("Expected element to be true, got %v", s.SlBool[0])
	}
	if s.SlBool[1] != false {
		t.Errorf("Expected element to be false, got %v", s.SlBool[1])
	}

	if len(s.SlStrSeq) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStrSeq))
	}
	if s.SlStrSeq[1] != "bar" {
		t.Errorf("Expected element to be bar, got %s", s.SlStrSeq[1])
	}
}

type mapValues struct {
	augeasPath string
	Entries    map[string]mapEntry `path:"mstruct"`
	MStr       map[string]string   `path:"sub/*" key:"label"`
}

type mapEntry struct {
	Str   string   `path:"str"`
	Int   int      `path:"int"`
	Bool  bool     `path:"bool"`
	SlStr []string `path:"slstr"`
}

func TestGetMapField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/mstruct[1]", "one")
	n.Augeas.Set("/test/mstruct[1]/str", "a")
	n.Augeas.Set("/test/mstruct[1]/int", "42")
	n.Augeas.Set("/test/mstruct[1]/bool", "true")
	n.Augeas.Set("/test/mstruct[1]/slstr[1]", "alpha")
	n.Augeas.Set("/test/mstruct[1]/slstr[2]", "beta")
	n.Augeas.Set("/test/mstruct[2]", "two")
	n.Augeas.Set("/test/mstruct[2]/str", "b")
	n.Augeas.Set("/test/mstruct[2]/int", "43")
	n.Augeas.Set("/test/mstruct[2]/bool", "false")
	n.Augeas.Set("/test/mstruct[2]/slstr[1]", "gamma")
	n.Augeas.Set("/test/mstruct[2]/slstr[2]", "delta")
	n.Augeas.Set("/test/sub/a", "aleph")
	n.Augeas.Set("/test/sub/b", "beth")
	m := &mapValues{
		augeasPath: "/test",
	}
	err = n.Parse(m)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(m.Entries) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries))
	}
	if m.Entries["two"].Str != "b" {
		t.Errorf("Expected element to be b, got %s", m.Entries["two"].Str)
	}
	if m.Entries["two"].Int != 43 {
		t.Errorf("Expected element to be 43, got %s", m.Entries["two"].Int)
	}
	if m.Entries["two"].Bool != false {
		t.Errorf("Expected element to be false, got %s", m.Entries["two"].Bool)
	}
	if len(m.Entries["two"].SlStr) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries["two"].SlStr))
	}
	if m.Entries["two"].SlStr[1] != "delta" {
		t.Errorf("Expected element to be delta, got %v", m.Entries["two"].SlStr[1])
	}

	if len(m.MStr) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.MStr))
	}
	if m.MStr["b"] != "beth" {
		t.Errorf("Expected element to be beth, got %s", m.MStr["b"])
	}
}

type noCapital struct {
	augeasPath string
	a          string `path:"a"`
}

func TestSetField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/a", "a")
	s := &noCapital{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "cannot set field a" {
		t.Errorf("Expected setField error, got %v", err)
	}
}
<commit_msg>Test slice of struct<commit_after>package narcissus

import (
	"testing"

	"honnef.co/go/augeas"
)

type foo struct {
	augeasPath string
	A          string `path:"a"`
}

type bar struct{}

func TestParseNotAPtr(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(foo{
		augeasPath: "/files/some/path",
	})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "invalid interface: not a ptr" {
		t.Errorf("Expected error not a ptr, got %s", err.Error())
	}
}

func TestParseNotAStruct(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	f := "foo"
	err = n.Parse(&f)

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "invalid interface: not a struct" {
		t.Errorf("Expected error not a struct, got %s", err.Error())
	}
}

func TestParseFieldNotFound(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{
		augeasPath: "/files/some/path",
	})

	t.Skip("Fix this")

	if err == nil {
		t.Error("Expected an error, got nothing")
	}
}

func TestNoAugeasPathValue(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "undefined path: no augeasPath value and no default" {
		t.Errorf("Expected error no augeasPath value and no default, got %s", err.Error())
	}
}

func TestNoAugeasPathField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&bar{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "undefined path: no augeasPath field" {
		t.Errorf("Expected error no augeasPath field, got %s", err.Error())
	}
}

type simpleValues struct {
	augeasPath string
	Str        string `path:"str"`
	Int        int    `path:"int"`
	Bool       bool   `path:"bool"`
}

func TestGetSimpleField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/str", "foo")
	n.Augeas.Set("/test/int", "42")
	n.Augeas.Set("/test/bool", "true")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if s.Str != "foo" {
		t.Errorf("Expected foo, got %s", s.Str)
	}

	if s.Int != 42 {
		t.Errorf("Expected 42, got %v", s.Int)
	}

	if s.Bool != true {
		t.Errorf("Expected true, got %v", s.Bool)
	}
}

func TestGetSimpleFieldWrongTypes(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/int", "a")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Int: failed to convert a to int: strconv.ParseInt: parsing \"a\": invalid syntax" {
		t.Errorf("Expected int conversion error, got %v", err)
	}

	n.Augeas.Remove("/test/int")
	n.Augeas.Set("/test/bool", "a")
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Bool: failed to convert a to bool: strconv.ParseBool: parsing \"a\": invalid syntax" {
		t.Errorf("Expected bool conversion error, got %v", err)
	}
}

type sliceValues struct {
	augeasPath string
	SlStr      []string   `path:"slstr"`
	SlInt      []int      `path:"slint"`
	SlBool     []bool     `path:"slbool"`
	SlStrSeq   []string   `type:"seq"`
	SlStruct   []mapEntry `path:"mapentry"`
}

func TestGetSliceField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	n.Augeas.Set("/test/1", "foo")
	n.Augeas.Set("/test/2", "bar")
	n.Augeas.Set("/test/mapentry[1]/str", "foo")
	n.Augeas.Set("/test/mapentry[1]/int", "314")
	n.Augeas.Set("/test/mapentry[1]/bool", "true")
	n.Augeas.Set("/test/mapentry[1]/slstr[1]", "aleph")
	n.Augeas.Set("/test/mapentry[1]/slstr[2]", "beth")
	n.Augeas.Set("/test/mapentry[2]/str", "bar")
	n.Augeas.Set("/test/mapentry[2]/int", "315")
	n.Augeas.Set("/test/mapentry[2]/bool", "false")
	n.Augeas.Set("/test/mapentry[2]/slstr[1]", "gimel")
	n.Augeas.Set("/test/mapentry[2]/slstr[2]", "daleth")
	s := &sliceValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(s.SlStr) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStr))
	}
	if s.SlStr[1] != "b" {
		t.Errorf("Expected element to be b, got %s", s.SlStr[1])
	}

	if len(s.SlInt) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlInt))
	}
	if s.SlInt[1] != 2 {
		t.Errorf("Expected element to be 2, got %v", s.SlInt[1])
	}

	if len(s.SlBool) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlBool))
	}
	if s.SlBool[0] != true {
		t.Errorf("Expected element to be true, got %v", s.SlBool[0])
	}
	if s.SlBool[1] != false {
		t.Errorf("Expected element to be false, got %v", s.SlBool[1])
	}

	if len(s.SlStrSeq) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStrSeq))
	}
	if s.SlStrSeq[1] != "bar" {
		t.Errorf("Expected element to be bar, got %s", s.SlStrSeq[1])
	}

	if len(s.SlStruct) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStruct))
	}
	if s.SlStruct[1].SlStr[1] != "daleth" {
		t.Errorf("Expected element to be daleth, got %s", s.SlStruct[1].SlStr[1])
	}
}

type mapValues struct {
	augeasPath string
	Entries    map[string]mapEntry `path:"mstruct"`
	MStr       map[string]string   `path:"sub/*" key:"label"`
}

type mapEntry struct {
	Str   string   `path:"str"`
	Int   int      `path:"int"`
	Bool  bool     `path:"bool"`
	SlStr []string `path:"slstr"`
}

func TestGetMapField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/mstruct[1]", "one")
	n.Augeas.Set("/test/mstruct[1]/str", "a")
	n.Augeas.Set("/test/mstruct[1]/int", "42")
	n.Augeas.Set("/test/mstruct[1]/bool", "true")
	n.Augeas.Set("/test/mstruct[1]/slstr[1]", "alpha")
	n.Augeas.Set("/test/mstruct[1]/slstr[2]", "beta")
	n.Augeas.Set("/test/mstruct[2]", "two")
	n.Augeas.Set("/test/mstruct[2]/str", "b")
	n.Augeas.Set("/test/mstruct[2]/int", "43")
	n.Augeas.Set("/test/mstruct[2]/bool", "false")
	n.Augeas.Set("/test/mstruct[2]/slstr[1]", "gamma")
	n.Augeas.Set("/test/mstruct[2]/slstr[2]", "delta")
	n.Augeas.Set("/test/sub/a", "aleph")
	n.Augeas.Set("/test/sub/b", "beth")
	m := &mapValues{
		augeasPath: "/test",
	}
	err = n.Parse(m)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(m.Entries) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries))
	}
	if m.Entries["two"].Str != "b" {
		t.Errorf("Expected element to be b, got %s", m.Entries["two"].Str)
	}
	if m.Entries["two"].Int != 43 {
		t.Errorf("Expected element to be 43, got %s", m.Entries["two"].Int)
	}
	if m.Entries["two"].Bool != false {
		t.Errorf("Expected element to be false, got %s", m.Entries["two"].Bool)
	}
	if len(m.Entries["two"].SlStr) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries["two"].SlStr))
	}
	if m.Entries["two"].SlStr[1] != "delta" {
		t.Errorf("Expected element to be delta, got %v", m.Entries["two"].SlStr[1])
	}

	if len(m.MStr) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.MStr))
	}
	if m.MStr["b"] != "beth" {
		t.Errorf("Expected element to be beth, got %s", m.MStr["b"])
	}
}

type noCapital struct {
	augeasPath string
	a          string `path:"a"`
}

func TestSetField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/a", "a")
	s := &noCapital{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "cannot set field a" {
		t.Errorf("Expected setField error, got %v", err)
	}
}
<|endoftext|>


<commit_before>package generator

import (
	"bytes"
	"fmt"
	"io"
	"strings"

	tl "github.com/xlab/cgogen/translator"
)

func unexportName(name string) string {
	if len(name) == 0 {
		return name
	}
	return strings.ToLower(name[:1]) + name[1:]
}

func (gen *Generator) getCallbackHelpers(goFuncName, cFuncName string, spec tl.CType) (helpers []*Helper) {
	crc := getRefCRC(spec)
	cbCName := fmt.Sprintf("%s_%2x", cFuncName, crc)
	cbGoName := fmt.Sprintf("%s%2X", unexportName(goFuncName), crc)
	funcSpec := spec.(*tl.CFunctionSpec)

	var params []string
	var paramNames []string
	var paramNamesGo []string
	for i, param := range funcSpec.Params {
		if len(param.Name) == 0 {
			param.Name = fmt.Sprintf("arg%d", i)
		}
		paramSpec := gen.tr.NormalizeSpecPointers(param.Spec)
		params = append(params, fmt.Sprintf("%s %s", paramSpec.AtLevel(0), param.Name))
		paramNames = append(paramNames, param.Name)
		goName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, false))
		paramNamesGo = append(paramNamesGo, fmt.Sprintf("%s%2x", goName, crc))
	}
	paramList := strings.Join(params, ", ")
	paramNamesList := strings.Join(paramNames, ", ")
	paramNamesGoList := strings.Join(paramNamesGo, ", ")

	buf := new(bytes.Buffer)
	retSpec := "void"
	if funcSpec.Return != nil {
		retSpec = funcSpec.Return.String()
	}
	fmt.Fprintf(buf, "%s %s(%s);", retSpec, cbCName, paramList)
	helpers = append(helpers, &Helper{
		Name:        cbCName,
		Description: fmt.Sprintf("%s is a proxy for callback %s.", cbCName, cFuncName),
		Source:      buf.String(),
		Side:        CHSide,
	})

	var ret string
	if funcSpec.Return != nil {
		ret = "return "
	}
	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "%s %s(%s) {\n", retSpec, cbCName, paramList)
	fmt.Fprintf(buf, "\t%s%s(%s);\n", ret, cbGoName, paramNamesList)
	buf.WriteRune('}')
	helpers = append(helpers, &Helper{
		Name:   cbCName,
		Source: buf.String(),
		Side:   CCSide,
	})

	cgoSpec := gen.tr.CGoSpec(&tl.CTypeSpec{
		Base: cFuncName,
	}, true)
	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "func (x %s) PassRef() (ref *%s, allocs *cgoAllocMap)", goFuncName, cgoSpec)
	fmt.Fprintf(buf, `{
		if x == nil {
			return nil, nil
		}
		if %sFunc == nil {
 			%sFunc = x
 		}
		return (*%s)(C.%s), nil
	}`, cbGoName, cbGoName, cgoSpec, cbCName)
	helpers = append(helpers, &Helper{
		Name:        fmt.Sprintf("%s.PassRef", goFuncName),
		Description: "PassRef returns a reference.",
		Source:      buf.String(),
	})

	if spec.GetPointers() > 0 {
		buf = new(bytes.Buffer)
		fmt.Fprintf(buf, "func (x %s) PassValue() (ref %s, allocs *cgoAllocMap)", goFuncName, cgoSpec)
		fmt.Fprintf(buf, `{
		if x == nil {
			return nil, nil
		}
		if %sFunc == nil {
 			%sFunc = x
 		}
		return (%s)(C.%s), nil
	}`, cbGoName, cbGoName, cgoSpec, cbCName)
		helpers = append(helpers, &Helper{
			Name:        fmt.Sprintf("%s.PassValue", goFuncName),
			Description: "PassValue returns a value.",
			Source:      buf.String(),
		})
	}

	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "//export %s\n", cbGoName)
	cbGoDecl := &tl.CDecl{
		Name: cbGoName,
		Spec: spec,
	}

	proxyLines := gen.createCallbackProxies(cFuncName, funcSpec)
	proxySrc := new(bytes.Buffer)
	for i := range proxyLines {
		proxySrc.WriteString(proxyLines[i].Decl)
	}

	gen.writeCallbackProxyFunc(buf, cbGoDecl)
	fmt.Fprintln(buf, "{")
	fmt.Fprintf(buf, "if %sFunc != nil {\n", cbGoName)
	buf.WriteString(proxySrc.String())
	if funcSpec.Return != nil {
		ret := fmt.Sprintf("ret%2x", crc)
		fmt.Fprintf(buf, "%s := %sFunc(%s)\n", ret, cbGoName, paramNamesGoList)
		ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, cFuncName, funcSpec)
		retGoSpec := gen.tr.TranslateSpec(funcSpec.Return, ptrTipRx.Self(), typeTipRx.Self())
		retCGoSpec := gen.tr.CGoSpec(funcSpec.Return, true) // asArg?
		retProxy, _ := gen.proxyArgFromGo(memTipRx.Self(), ret, retGoSpec, retCGoSpec)
		fmt.Fprintf(buf, "ret, _ := %s\n", retProxy)
		fmt.Fprintf(buf, "return ret\n")
	} else {
		fmt.Fprintf(buf, "%sFunc(%s)\n", cbGoName, paramNamesGoList)
	}
	fmt.Fprintln(buf, "}")
	fmt.Fprintln(buf, `panic("callback func has not been set (race?)")`)
	fmt.Fprintln(buf, "}")

	fmt.Fprintf(buf, "\n\nvar %sFunc %s", cbGoName, goFuncName)
	helpers = append(helpers, &Helper{
		Name:   cbGoName,
		Source: buf.String(),
	})
	return
}

func (gen *Generator) writeCallbackProxyFunc(wr io.Writer, decl *tl.CDecl) {
	var returnRef string
	funcSpec := decl.Spec.(*tl.CFunctionSpec)
	if funcSpec.Return != nil {
		cgoSpec := gen.tr.CGoSpec(funcSpec.Return, true) // asArg?
		returnRef = cgoSpec.String()
	}
	fmt.Fprintf(wr, "func %s", decl.Name)
	gen.writeCallbackProxyFuncParams(wr, decl.Spec)
	if len(returnRef) > 0 {
		fmt.Fprintf(wr, " %s", returnRef)
	}
}

func (gen *Generator) writeCallbackProxyFuncParams(wr io.Writer, spec tl.CType) {
	funcSpec := spec.(*tl.CFunctionSpec)
	const public = false

	writeStartParams(wr)
	for i, param := range funcSpec.Params {
		declName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, public))
		if len(param.Name) == 0 {
			declName = []byte(fmt.Sprintf("arg%d", i))
		}
		cgoSpec := gen.tr.CGoSpec(param.Spec, true)
		fmt.Fprintf(wr, "c%s %s", declName, cgoSpec.AtLevel(0))

		if i < len(funcSpec.Params)-1 {
			fmt.Fprintf(wr, ", ")
		}
	}
	writeEndParams(wr)
}

func (gen *Generator) createCallbackProxies(funcName string, funcSpec tl.CType) (to []proxyDecl) {
	spec := funcSpec.(*tl.CFunctionSpec)
	to = make([]proxyDecl, 0, len(spec.Params))

	crc := getRefCRC(funcSpec)
	ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, funcName, funcSpec)
	for i, param := range spec.Params {
		var goSpec tl.GoTypeSpec
		ptrTip := ptrTipRx.TipAt(i)
		typeTip := typeTipRx.TipAt(i)
		goSpec = gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip)
		cgoSpec := gen.tr.CGoSpec(param.Spec, true)
		const public = false
		refName := string(gen.tr.TransformName(tl.TargetType, param.Name, public))
		if len(param.Name) == 0 {
			refName = fmt.Sprintf("arg%d", i)
		}
		toBuf := new(bytes.Buffer)
		cname := "c" + refName
		refName = fmt.Sprintf("%s%2x", refName, crc)
		toProxy, _ := gen.proxyCallbackArgToGo(memTipRx.TipAt(i), refName, cname, goSpec, cgoSpec)
		if len(toProxy) > 0 {
			fmt.Fprintln(toBuf, toProxy)
			to = append(to, proxyDecl{Name: cname, Decl: toBuf.String()})
		}
	}
	return
}

func (gen *Generator) proxyCallbackArgToGo(memTip tl.Tip, varName, ptrName string,
	goSpec tl.GoTypeSpec, cgoSpec tl.CGoSpec) (proxy string, nillable bool) {
	nillable = true

	if getHelper, ok := toGoHelperMap[goSpec]; ok {
		helper := getHelper(gen, cgoSpec)
		gen.submitHelper(helper)
		proxy = fmt.Sprintf("%s := %s(%s)", varName, helper.Name, ptrName)
		return proxy, helper.Nillable
	}
	gen.submitHelper(cgoAllocMap)

	isPlain := (memTip == tl.TipMemRaw) || goSpec.IsPlain() || goSpec.IsPlainKind()
	switch {
	case !isPlain && (goSpec.Slices > 0 || len(goSpec.OuterArr) > 0), // ex: []string
		isPlain && goSpec.Slices > 0 && len(goSpec.OuterArr) > 0, // ex: [4][]byte
		isPlain && goSpec.Slices > 1:                             // ex: [][]byte
		helper := gen.getPackHelper(memTip, goSpec, cgoSpec)
		gen.submitHelper(helper)
		if len(goSpec.OuterArr) > 0 {
			ptrName = fmt.Sprintf("%s := (*%s)(unsafe.Pointer(&%s))", varName, cgoSpec, ptrName)
		}
		gen.submitHelper(sliceHeader)
		proxy = fmt.Sprintf("var %s %s\n%s(%s, %s)", varName, goSpec, helper.Name, varName, ptrName)
		return proxy, helper.Nillable
	case isPlain && goSpec.Slices != 0: // ex: []byte
		gen.submitHelper(sliceHeader)
		buf := new(bytes.Buffer)
		postfix := gen.randPostfix()
		fmt.Fprintf(buf, "var %s %s\n", varName, goSpec)
		fmt.Fprintf(buf, "hx%2x := (*sliceHeader)(unsafe.Pointer(&%s))\n", postfix, varName)
		fmt.Fprintf(buf, "hx%2x.Data = uintptr(unsafe.Pointer(%s))\n", postfix, ptrName)
		fmt.Fprintf(buf, "hx%2x.Cap = 0x7fffffff\n", postfix)
		fmt.Fprintf(buf, "// hx%2x.Len = ?\n", postfix)
		proxy = buf.String()
		return
	case isPlain: // ex: byte, [4]byte
		var ref, ptr string
		if (goSpec.Kind == tl.PlainTypeKind || goSpec.Kind == tl.EnumKind) &&
			len(goSpec.OuterArr) == 0 && goSpec.Pointers == 0 {
			proxy = fmt.Sprintf("%s := (%s)(%s)", varName, goSpec, ptrName)
			return
		} else if goSpec.Pointers == 0 {
			ref = "&"
			ptr = "*"
		}
		proxy = fmt.Sprintf("%s := %s(%s%s)(unsafe.Pointer(%s%s))", varName, ptr, ptr, goSpec, ref, ptrName)
		return
	default: // ex: *SomeType
		var ref, deref string
		if cgoSpec.Pointers == 0 {
			deref = "*"
			ref = "&"
		}
		proxy = fmt.Sprintf("%s := %sNew%sRef(unsafe.Pointer(%s%s))", varName, deref, goSpec.Raw, ref, ptrName)
		return
	}
}
<commit_msg>Fix callbacks with no return values.<commit_after>package generator

import (
	"bytes"
	"fmt"
	"io"
	"strings"

	tl "github.com/xlab/cgogen/translator"
)

func unexportName(name string) string {
	if len(name) == 0 {
		return name
	}
	return strings.ToLower(name[:1]) + name[1:]
}

func (gen *Generator) getCallbackHelpers(goFuncName, cFuncName string, spec tl.CType) (helpers []*Helper) {
	crc := getRefCRC(spec)
	cbCName := fmt.Sprintf("%s_%2x", cFuncName, crc)
	cbGoName := fmt.Sprintf("%s%2X", unexportName(goFuncName), crc)
	funcSpec := spec.(*tl.CFunctionSpec)

	var params []string
	var paramNames []string
	var paramNamesGo []string
	for i, param := range funcSpec.Params {
		if len(param.Name) == 0 {
			param.Name = fmt.Sprintf("arg%d", i)
		}
		paramSpec := gen.tr.NormalizeSpecPointers(param.Spec)
		params = append(params, fmt.Sprintf("%s %s", paramSpec.AtLevel(0), param.Name))
		paramNames = append(paramNames, param.Name)
		goName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, false))
		paramNamesGo = append(paramNamesGo, fmt.Sprintf("%s%2x", goName, crc))
	}
	paramList := strings.Join(params, ", ")
	paramNamesList := strings.Join(paramNames, ", ")
	paramNamesGoList := strings.Join(paramNamesGo, ", ")

	buf := new(bytes.Buffer)
	retSpec := "void"
	if funcSpec.Return != nil {
		retSpec = funcSpec.Return.String()
	}
	fmt.Fprintf(buf, "%s %s(%s);", retSpec, cbCName, paramList)
	helpers = append(helpers, &Helper{
		Name:        cbCName,
		Description: fmt.Sprintf("%s is a proxy for callback %s.", cbCName, cFuncName),
		Source:      buf.String(),
		Side:        CHSide,
	})

	var ret string
	if funcSpec.Return != nil {
		ret = "return "
	}
	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "%s %s(%s) {\n", retSpec, cbCName, paramList)
	fmt.Fprintf(buf, "\t%s%s(%s);\n", ret, cbGoName, paramNamesList)
	buf.WriteRune('}')
	helpers = append(helpers, &Helper{
		Name:   cbCName,
		Source: buf.String(),
		Side:   CCSide,
	})

	cgoSpec := gen.tr.CGoSpec(&tl.CTypeSpec{
		Base: cFuncName,
	}, true)
	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "func (x %s) PassRef() (ref *%s, allocs *cgoAllocMap)", goFuncName, cgoSpec)
	fmt.Fprintf(buf, `{
		if x == nil {
			return nil, nil
		}
		if %sFunc == nil {
 			%sFunc = x
 		}
		return (*%s)(C.%s), nil
	}`, cbGoName, cbGoName, cgoSpec, cbCName)
	helpers = append(helpers, &Helper{
		Name:        fmt.Sprintf("%s.PassRef", goFuncName),
		Description: "PassRef returns a reference.",
		Source:      buf.String(),
	})

	if spec.GetPointers() > 0 {
		buf = new(bytes.Buffer)
		fmt.Fprintf(buf, "func (x %s) PassValue() (ref %s, allocs *cgoAllocMap)", goFuncName, cgoSpec)
		fmt.Fprintf(buf, `{
		if x == nil {
			return nil, nil
		}
		if %sFunc == nil {
 			%sFunc = x
 		}
		return (%s)(C.%s), nil
	}`, cbGoName, cbGoName, cgoSpec, cbCName)
		helpers = append(helpers, &Helper{
			Name:        fmt.Sprintf("%s.PassValue", goFuncName),
			Description: "PassValue returns a value.",
			Source:      buf.String(),
		})
	}

	buf = new(bytes.Buffer)
	fmt.Fprintf(buf, "//export %s\n", cbGoName)
	cbGoDecl := &tl.CDecl{
		Name: cbGoName,
		Spec: spec,
	}

	proxyLines := gen.createCallbackProxies(cFuncName, funcSpec)
	proxySrc := new(bytes.Buffer)
	for i := range proxyLines {
		proxySrc.WriteString(proxyLines[i].Decl)
	}

	gen.writeCallbackProxyFunc(buf, cbGoDecl)
	fmt.Fprintln(buf, "{")
	fmt.Fprintf(buf, "if %sFunc != nil {\n", cbGoName)
	buf.WriteString(proxySrc.String())
	if funcSpec.Return != nil {
		ret := fmt.Sprintf("ret%2x", crc)
		fmt.Fprintf(buf, "%s := %sFunc(%s)\n", ret, cbGoName, paramNamesGoList)
		ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, cFuncName, funcSpec)
		retGoSpec := gen.tr.TranslateSpec(funcSpec.Return, ptrTipRx.Self(), typeTipRx.Self())
		retCGoSpec := gen.tr.CGoSpec(funcSpec.Return, true) // asArg?
		retProxy, _ := gen.proxyArgFromGo(memTipRx.Self(), ret, retGoSpec, retCGoSpec)
		fmt.Fprintf(buf, "ret, _ := %s\n", retProxy)
		fmt.Fprintf(buf, "return ret\n")
	} else {
		fmt.Fprintf(buf, "%sFunc(%s)\n", cbGoName, paramNamesGoList)
		fmt.Fprintln(buf, "return")
	}
	fmt.Fprintln(buf, `panic("callback func has not been set (race?)")`)
	fmt.Fprintln(buf, "}")
	fmt.Fprintln(buf, "}")

	fmt.Fprintf(buf, "\n\nvar %sFunc %s", cbGoName, goFuncName)
	helpers = append(helpers, &Helper{
		Name:   cbGoName,
		Source: buf.String(),
	})
	return
}

func (gen *Generator) writeCallbackProxyFunc(wr io.Writer, decl *tl.CDecl) {
	var returnRef string
	funcSpec := decl.Spec.(*tl.CFunctionSpec)
	if funcSpec.Return != nil {
		cgoSpec := gen.tr.CGoSpec(funcSpec.Return, true) // asArg?
		returnRef = cgoSpec.String()
	}
	fmt.Fprintf(wr, "func %s", decl.Name)
	gen.writeCallbackProxyFuncParams(wr, decl.Spec)
	if len(returnRef) > 0 {
		fmt.Fprintf(wr, " %s", returnRef)
	}
}

func (gen *Generator) writeCallbackProxyFuncParams(wr io.Writer, spec tl.CType) {
	funcSpec := spec.(*tl.CFunctionSpec)
	const public = false

	writeStartParams(wr)
	for i, param := range funcSpec.Params {
		declName := checkName(gen.tr.TransformName(tl.TargetType, param.Name, public))
		if len(param.Name) == 0 {
			declName = []byte(fmt.Sprintf("arg%d", i))
		}
		cgoSpec := gen.tr.CGoSpec(param.Spec, true)
		fmt.Fprintf(wr, "c%s %s", declName, cgoSpec.AtLevel(0))

		if i < len(funcSpec.Params)-1 {
			fmt.Fprintf(wr, ", ")
		}
	}
	writeEndParams(wr)
}

func (gen *Generator) createCallbackProxies(funcName string, funcSpec tl.CType) (to []proxyDecl) {
	spec := funcSpec.(*tl.CFunctionSpec)
	to = make([]proxyDecl, 0, len(spec.Params))

	crc := getRefCRC(funcSpec)
	ptrTipRx, typeTipRx, memTipRx := gen.tr.TipRxsForSpec(tl.TipScopeFunction, funcName, funcSpec)
	for i, param := range spec.Params {
		var goSpec tl.GoTypeSpec
		ptrTip := ptrTipRx.TipAt(i)
		typeTip := typeTipRx.TipAt(i)
		goSpec = gen.tr.TranslateSpec(param.Spec, ptrTip, typeTip)
		cgoSpec := gen.tr.CGoSpec(param.Spec, true)
		const public = false
		refName := string(gen.tr.TransformName(tl.TargetType, param.Name, public))
		if len(param.Name) == 0 {
			refName = fmt.Sprintf("arg%d", i)
		}
		toBuf := new(bytes.Buffer)
		cname := "c" + refName
		refName = fmt.Sprintf("%s%2x", refName, crc)
		toProxy, _ := gen.proxyCallbackArgToGo(memTipRx.TipAt(i), refName, cname, goSpec, cgoSpec)
		if len(toProxy) > 0 {
			fmt.Fprintln(toBuf, toProxy)
			to = append(to, proxyDecl{Name: cname, Decl: toBuf.String()})
		}
	}
	return
}

func (gen *Generator) proxyCallbackArgToGo(memTip tl.Tip, varName, ptrName string,
	goSpec tl.GoTypeSpec, cgoSpec tl.CGoSpec) (proxy string, nillable bool) {
	nillable = true

	if getHelper, ok := toGoHelperMap[goSpec]; ok {
		helper := getHelper(gen, cgoSpec)
		gen.submitHelper(helper)
		proxy = fmt.Sprintf("%s := %s(%s)", varName, helper.Name, ptrName)
		return proxy, helper.Nillable
	}
	gen.submitHelper(cgoAllocMap)

	isPlain := (memTip == tl.TipMemRaw) || goSpec.IsPlain() || goSpec.IsPlainKind()
	switch {
	case !isPlain && (goSpec.Slices > 0 || len(goSpec.OuterArr) > 0), // ex: []string
		isPlain && goSpec.Slices > 0 && len(goSpec.OuterArr) > 0, // ex: [4][]byte
		isPlain && goSpec.Slices > 1:                             // ex: [][]byte
		helper := gen.getPackHelper(memTip, goSpec, cgoSpec)
		gen.submitHelper(helper)
		if len(goSpec.OuterArr) > 0 {
			ptrName = fmt.Sprintf("%s := (*%s)(unsafe.Pointer(&%s))", varName, cgoSpec, ptrName)
		}
		gen.submitHelper(sliceHeader)
		proxy = fmt.Sprintf("var %s %s\n%s(%s, %s)", varName, goSpec, helper.Name, varName, ptrName)
		return proxy, helper.Nillable
	case isPlain && goSpec.Slices != 0: // ex: []byte
		gen.submitHelper(sliceHeader)
		buf := new(bytes.Buffer)
		postfix := gen.randPostfix()
		fmt.Fprintf(buf, "var %s %s\n", varName, goSpec)
		fmt.Fprintf(buf, "hx%2x := (*sliceHeader)(unsafe.Pointer(&%s))\n", postfix, varName)
		fmt.Fprintf(buf, "hx%2x.Data = uintptr(unsafe.Pointer(%s))\n", postfix, ptrName)
		fmt.Fprintf(buf, "hx%2x.Cap = 0x7fffffff\n", postfix)
		fmt.Fprintf(buf, "// hx%2x.Len = ?\n", postfix)
		proxy = buf.String()
		return
	case isPlain: // ex: byte, [4]byte
		var ref, ptr string
		if (goSpec.Kind == tl.PlainTypeKind || goSpec.Kind == tl.EnumKind) &&
			len(goSpec.OuterArr) == 0 && goSpec.Pointers == 0 {
			proxy = fmt.Sprintf("%s := (%s)(%s)", varName, goSpec, ptrName)
			return
		} else if goSpec.Pointers == 0 {
			ref = "&"
			ptr = "*"
		}
		proxy = fmt.Sprintf("%s := %s(%s%s)(unsafe.Pointer(%s%s))", varName, ptr, ptr, goSpec, ref, ptrName)
		return
	default: // ex: *SomeType
		var ref, deref string
		if cgoSpec.Pointers == 0 {
			deref = "*"
			ref = "&"
		}
		proxy = fmt.Sprintf("%s := %sNew%sRef(unsafe.Pointer(%s%s))", varName, deref, goSpec.Raw, ref, ptrName)
		return
	}
}
<|endoftext|>


<commit_before>package pathfinder

import (
	"path/filepath"
	"strconv"
	"time"
)

func MinutesDataDir(date time.Time) string {
	y, m, d := date.Date()
	return filepath.Join("data", strconv.Itoa(y),
		strconv.Itoa(int(m)), strconv.Itoa(d))
}
<commit_msg>Easy format for creating time.Time and comments<commit_after>package pathfinder

import (
	"path/filepath"
	"strconv"
	"time"
)

//Returns a time.Date object based on the Year, Month, and Date only
func EasyDate(year, month, date int) time.Time {
	return time.Date(year, time.Month(month), date, 0, 0, 0, 0, time.UTC)
}

//Returns a path from the projects main directory to a meeting date's
//data directory. Formatted as follows
//project_dir/data/
//    2014/
//        /<month/
//            /<day of filing>/
//                minutes.pdf
//                log?
//                processed_data_files
func MinutesDataDir(date time.Time) string {
	y, m, d := date.Date()
	return filepath.Join("data", strconv.Itoa(y),
		strconv.Itoa(int(m)), strconv.Itoa(d))
}
<|endoftext|>


<commit_before>package log

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"testing"
	"time"
)

func TestRouter(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name     string
		fields   Fields
		expected string
	}{
		{"empty", nil, `{}` + "\n"},
		{"nil", Fields{"undefined": nil}, `{"undefined":null}` + "\n"},
		{"string", Fields{"string": "message1"}, `{"string":"message1"}` + "\n"},
		{"int", Fields{"number": 42}, `{"number":42}` + "\n"},
		{"float", Fields{"number": 99.1}, `{"number":99.1}` + "\n"},
		{"bool", Fields{"bool": true}, `{"bool":true}` + "\n"},
		{"object", Fields{"object": Fields{"key1": "value1"}}, `{"object":{"key1":"value1"}}` + "\n"},
		{"array_of_nils", Fields{"x": [2]interface{}{nil, nil}}, `{"x":[null,null]}` + "\n"},
		{"array_of_strings", Fields{"x": [2]interface{}{"msg1", "msg2"}}, `{"x":["msg1","msg2"]}` + "\n"},
		{"array_of_ints", Fields{"x": [2]interface{}{42, 82}}, `{"x":[42,82]}` + "\n"},
		{"array_of_floats", Fields{"x": [2]interface{}{99.1, 33.1}}, `{"x":[99.1,33.1]}` + "\n"},
		{"array_of_bools", Fields{"x": [2]interface{}{true, false}}, `{"x":[true,false]}` + "\n"},
		{"array_of_objects", Fields{"x": [2]interface{}{Fields{"key1": "msg1"}, Fields{"key2": "msg2"}}},
			`{"x":[{"key1":"msg1"},{"key2":"msg2"}]}` + "\n"},
		{"array_of_mixed", Fields{"x": [6]interface{}{nil, "msg", 42, 33.6, true, Fields{"key1": "msg1"}}},
			`{"x":[null,"msg",42,33.6,true,{"key1":"msg1"}]}` + "\n"},
		{"slice_of_nils", Fields{"x": []interface{}{nil, nil}}, `{"x":[null,null]}` + "\n"},
		{"slice_of_strings", Fields{"x": []interface{}{"msg1", "msg2"}}, `{"x":["msg1","msg2"]}` + "\n"},
		{"slice_of_ints", Fields{"x": []interface{}{42, 82}}, `{"x":[42,82]}` + "\n"},
		{"slice_of_floats", Fields{"x": []interface{}{99.1, 33.1}}, `{"x":[99.1,33.1]}` + "\n"},
		{"slice_of_bools", Fields{"x": []interface{}{true, false}}, `{"x":[true,false]}` + "\n"},
		{"slice_of_objects", Fields{"x": []interface{}{Fields{"key1": "msg1"}, Fields{"key2": "msg2"}}},
			`{"x":[{"key1":"msg1"},{"key2":"msg2"}]}` + "\n"},
		{"slice_of_mixed", Fields{"x": []interface{}{nil, "msg", 42, 33.6, true, Fields{"key1": "msg1"}}},
			`{"x":[null,"msg",42,33.6,true,{"key1":"msg1"}]}` + "\n"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			r := NewRouter()
			buf := &bytes.Buffer{}
			r.Output("output1", buf, nil, nil)
			r.Log(tc.fields)

			actual := buf.String()
			if actual != tc.expected {
				t.Fatalf("expected %q, but got: %q", tc.expected, actual)
			}
		})
	}
}

func TestLogger(t *testing.T) {
	t.Parallel()

	rfc3339Re := regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$`)
	shortfileRe := regexp.MustCompile(`log_test.go:[0-9]+$`)
	longfileRe := regexp.MustCompile(`.+(\\|/)log_test.go:[0-9]+$`)

	type e struct {
		field   string
		pattern interface{}
	}

	testCases := []struct {
		testName string
		config   Config
		fields   Fields
		expected []*e
	}{
		{"rfc3339", Config{TimeFormat: time.RFC3339}, nil, []*e{{"time", rfc3339Re}}},
		{"rfc3339 utc", Config{TimeFormat: time.RFC3339, UTC: true}, nil, []*e{{"time", rfc3339Re}}},
		{"logger name", Config{Name: "duck"}, nil, []*e{{"logger", "duck"}}},
		{"short file line", Config{FileLine: ShortFileLine}, nil, []*e{{"file", shortfileRe}}},
		{"long file line", Config{FileLine: LongFileLine}, nil, []*e{{"file", longfileRe}}},
		{"rfc3339 logger", Config{Name: "logger", TimeFormat: time.RFC3339}, nil, []*e{{"time", rfc3339Re}, {"logger", "logger"}}},
		{"sort fields1", Config{SortFields: true}, nil, []*e{{"_sort", true}}},
		{"sort fields2", Config{SortFields: false}, nil, []*e{{"_sort", false}}},
		{"custom time", Config{TimeFormat: time.RFC3339}, Fields{"time": "now1"}, []*e{{"time", "now1"}}},
		{"custom logger name", Config{Name: "monkey"}, Fields{"logger": "elephant"}, []*e{{"logger", "elephant"}}},
		{"custom short file line", Config{FileLine: ShortFileLine}, Fields{"file": "line1"}, []*e{{"file", "line1"}}},
		{"custom long file line", Config{FileLine: LongFileLine}, Fields{"file": "line2"}, []*e{{"file", "line2"}}},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()

			spy := &routerSpy{}
			tc.config.Router = spy
			l := NewLogger(tc.config)
			l.Log(tc.fields)

			for _, e := range tc.expected {
				actual, ok := spy.fields[e.field]
				if !ok {
					t.Fatalf("field not found: %s", e.field)
				}

				if re, ok := e.pattern.(*regexp.Regexp); ok {
					if !re.MatchString(fmt.Sprintf("%v", actual)) {
						t.Fatalf("expected %v, but got %v", re.String(), actual)
					}
				} else {
					if actual != e.pattern {
						t.Fatalf("expected %v, but got %v", e.pattern, actual)
					}
				}
			}
		})
	}
}

// no goroutine safe
type routerSpy struct {
	fields Fields
}

func (r *routerSpy) Output(id string, w io.Writer, formatter Formatter, filter Filter) {}

func (r *routerSpy) Log(fields Fields) {
	r.fields = fields
}

func (r *routerSpy) OnError(f func(id string, w io.Writer, err error)) {}

func TestFiltersComposite(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName string
		filter   Filter
		expected bool
	}{
		{"and1", And(&mockFilter{r: false}, &mockFilter{r: false}), false},
		{"and2", And(&mockFilter{r: false}, &mockFilter{r: true}), false},
		{"and3", And(&mockFilter{r: true}, &mockFilter{r: false}), false},
		{"and4", And(&mockFilter{r: true}, &mockFilter{r: true}), true},

		{"or1", Or(&mockFilter{r: false}, &mockFilter{r: false}), false},
		{"or2", Or(&mockFilter{r: false}, &mockFilter{r: true}), true},
		{"or3", Or(&mockFilter{r: true}, &mockFilter{r: false}), true},
		{"or4", Or(&mockFilter{r: true}, &mockFilter{r: true}), true},

		{"not1", Not(&mockFilter{r: true}), false},
		{"not2", Not(&mockFilter{r: false}), true},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			match, err := tc.filter.Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expected {
				t.Fatalf("expected %v, but got %v", tc.expected, match)
			}
		})
	}
}

type mockFilter struct {
	r   bool
	n   string
	buf *bytes.Buffer
}

func (m *mockFilter) Match(fields Fields) (bool, error) {
	if m.buf != nil {
		m.buf.Write([]byte(m.n))
	}
	return m.r, nil
}

func TestFilters(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName string
		filter   Filter
		fields   Fields
		expected bool
	}{
		{"field exist", FieldExist("time"), Fields{"time": 123}, true},
		{"field not exist", FieldExist("time2"), Fields{"time": 123}, false},
		{"field exist dotpath", FieldExist("user.id"), Fields{"user": Fields{"id": 1}}, true},
		{"field not exist dotpath", FieldExist("user.username"), Fields{"user": Fields{"id": 1}}, false},

		{"eq string", Eq("logger", "requestLogger"), Fields{"logger": "requestLogger"}, true},
		{"not eq string", Eq("logger", "requestLogger2"), Fields{"logger": "requestLogger"}, false},
		{"eq string dotpath", Eq("user.id", 1), Fields{"user": Fields{"id": 1}}, true},
		{"not eq string dotpath", Eq("user.id", 2), Fields{"user": Fields{"id": 1}}, false},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			match, err := tc.filter.Match(tc.fields)
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expected {
				t.Fatalf("expected %v, but got %v", tc.expected, match)
			}
		})
	}
}

func TestAndShortCircuit(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName      string
		filters       []Filter
		expectedMatch bool
		expectedOrder string
	}{
		{"and1", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "A"},
		{"and2", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: true, n: "B"}}, false, "A"},
		{"and3", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "AB"},
		{"and4", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "AB"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			buf := &bytes.Buffer{}
			for _, f := range tc.filters {
				(f.(*mockFilter)).buf = buf
			}

			match, err := And(tc.filters...).Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expectedMatch {
				t.Fatalf("expected %v, but got %v", tc.expectedMatch, match)
			}
			if buf.String() != tc.expectedOrder {
				t.Fatalf("expected %v, but got %v", tc.expectedOrder, buf.String())
			}
		})
	}
}

func TestOrShortCircuit(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName      string
		filters       []Filter
		expectedMatch bool
		expectedOrder string
	}{
		{"or1", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "AB"},
		{"or2", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "AB"},
		{"or3", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: false, n: "B"}}, true, "A"},
		{"or4", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "A"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			buf := &bytes.Buffer{}
			for _, f := range tc.filters {
				(f.(*mockFilter)).buf = buf
			}

			match, err := Or(tc.filters...).Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expectedMatch {
				t.Fatalf("expected %v, but got %v", tc.expectedMatch, match)
			}
			if buf.String() != tc.expectedOrder {
				t.Fatalf("expected %v, but got %v", tc.expectedOrder, buf.String())
			}
		})
	}
}
<commit_msg>test for ignoring keys that begin with underscore<commit_after>package log

import (
	"bytes"
	"fmt"
	"io"
	"regexp"
	"testing"
	"time"
)

func TestRouter(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name     string
		fields   Fields
		expected string
	}{
		{"empty", nil, `{}` + "\n"},
		{"nil", Fields{"undefined": nil}, `{"undefined":null}` + "\n"},
		{"string", Fields{"string": "message1"}, `{"string":"message1"}` + "\n"},
		{"int", Fields{"number": 42}, `{"number":42}` + "\n"},
		{"float", Fields{"number": 99.1}, `{"number":99.1}` + "\n"},
		{"bool", Fields{"bool": true}, `{"bool":true}` + "\n"},
		{"object", Fields{"object": Fields{"key1": "value1"}}, `{"object":{"key1":"value1"}}` + "\n"},
		{"array_of_nils", Fields{"x": [2]interface{}{nil, nil}}, `{"x":[null,null]}` + "\n"},
		{"array_of_strings", Fields{"x": [2]interface{}{"msg1", "msg2"}}, `{"x":["msg1","msg2"]}` + "\n"},
		{"array_of_ints", Fields{"x": [2]interface{}{42, 82}}, `{"x":[42,82]}` + "\n"},
		{"array_of_floats", Fields{"x": [2]interface{}{99.1, 33.1}}, `{"x":[99.1,33.1]}` + "\n"},
		{"array_of_bools", Fields{"x": [2]interface{}{true, false}}, `{"x":[true,false]}` + "\n"},
		{"array_of_objects", Fields{"x": [2]interface{}{Fields{"key1": "msg1"}, Fields{"key2": "msg2"}}},
			`{"x":[{"key1":"msg1"},{"key2":"msg2"}]}` + "\n"},
		{"array_of_mixed", Fields{"x": [6]interface{}{nil, "msg", 42, 33.6, true, Fields{"key1": "msg1"}}},
			`{"x":[null,"msg",42,33.6,true,{"key1":"msg1"}]}` + "\n"},
		{"slice_of_nils", Fields{"x": []interface{}{nil, nil}}, `{"x":[null,null]}` + "\n"},
		{"slice_of_strings", Fields{"x": []interface{}{"msg1", "msg2"}}, `{"x":["msg1","msg2"]}` + "\n"},
		{"slice_of_ints", Fields{"x": []interface{}{42, 82}}, `{"x":[42,82]}` + "\n"},
		{"slice_of_floats", Fields{"x": []interface{}{99.1, 33.1}}, `{"x":[99.1,33.1]}` + "\n"},
		{"slice_of_bools", Fields{"x": []interface{}{true, false}}, `{"x":[true,false]}` + "\n"},
		{"slice_of_objects", Fields{"x": []interface{}{Fields{"key1": "msg1"}, Fields{"key2": "msg2"}}},
			`{"x":[{"key1":"msg1"},{"key2":"msg2"}]}` + "\n"},
		{"slice_of_mixed", Fields{"x": []interface{}{nil, "msg", 42, 33.6, true, Fields{"key1": "msg1"}}},
			`{"x":[null,"msg",42,33.6,true,{"key1":"msg1"}]}` + "\n"},
		{"ignore underscore", Fields{"_ignoreit": "abc"}, `{}` + "\n"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			r := NewRouter()
			buf := &bytes.Buffer{}
			r.Output("output1", buf, nil, nil)
			r.Log(tc.fields)

			actual := buf.String()
			if actual != tc.expected {
				t.Fatalf("expected %q, but got: %q", tc.expected, actual)
			}
		})
	}
}

func TestLogger(t *testing.T) {
	t.Parallel()

	rfc3339Re := regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$`)
	shortfileRe := regexp.MustCompile(`log_test.go:[0-9]+$`)
	longfileRe := regexp.MustCompile(`.+(\\|/)log_test.go:[0-9]+$`)

	type e struct {
		field   string
		pattern interface{}
	}

	testCases := []struct {
		testName string
		config   Config
		fields   Fields
		expected []*e
	}{
		{"rfc3339", Config{TimeFormat: time.RFC3339}, nil, []*e{{"time", rfc3339Re}}},
		{"rfc3339 utc", Config{TimeFormat: time.RFC3339, UTC: true}, nil, []*e{{"time", rfc3339Re}}},
		{"logger name", Config{Name: "duck"}, nil, []*e{{"logger", "duck"}}},
		{"short file line", Config{FileLine: ShortFileLine}, nil, []*e{{"file", shortfileRe}}},
		{"long file line", Config{FileLine: LongFileLine}, nil, []*e{{"file", longfileRe}}},
		{"rfc3339 logger", Config{Name: "logger", TimeFormat: time.RFC3339}, nil, []*e{{"time", rfc3339Re}, {"logger", "logger"}}},
		{"sort fields1", Config{SortFields: true}, nil, []*e{{"_sort", true}}},
		{"sort fields2", Config{SortFields: false}, nil, []*e{{"_sort", false}}},
		{"custom time", Config{TimeFormat: time.RFC3339}, Fields{"time": "now1"}, []*e{{"time", "now1"}}},
		{"custom logger name", Config{Name: "monkey"}, Fields{"logger": "elephant"}, []*e{{"logger", "elephant"}}},
		{"custom short file line", Config{FileLine: ShortFileLine}, Fields{"file": "line1"}, []*e{{"file", "line1"}}},
		{"custom long file line", Config{FileLine: LongFileLine}, Fields{"file": "line2"}, []*e{{"file", "line2"}}},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()

			spy := &routerSpy{}
			tc.config.Router = spy
			l := NewLogger(tc.config)
			l.Log(tc.fields)

			for _, e := range tc.expected {
				actual, ok := spy.fields[e.field]
				if !ok {
					t.Fatalf("field not found: %s", e.field)
				}

				if re, ok := e.pattern.(*regexp.Regexp); ok {
					if !re.MatchString(fmt.Sprintf("%v", actual)) {
						t.Fatalf("expected %v, but got %v", re.String(), actual)
					}
				} else {
					if actual != e.pattern {
						t.Fatalf("expected %v, but got %v", e.pattern, actual)
					}
				}
			}
		})
	}
}

// no goroutine safe
type routerSpy struct {
	fields Fields
}

func (r *routerSpy) Output(id string, w io.Writer, formatter Formatter, filter Filter) {}

func (r *routerSpy) Log(fields Fields) {
	r.fields = fields
}

func (r *routerSpy) OnError(f func(id string, w io.Writer, err error)) {}

func TestFiltersComposite(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName string
		filter   Filter
		expected bool
	}{
		{"and1", And(&mockFilter{r: false}, &mockFilter{r: false}), false},
		{"and2", And(&mockFilter{r: false}, &mockFilter{r: true}), false},
		{"and3", And(&mockFilter{r: true}, &mockFilter{r: false}), false},
		{"and4", And(&mockFilter{r: true}, &mockFilter{r: true}), true},

		{"or1", Or(&mockFilter{r: false}, &mockFilter{r: false}), false},
		{"or2", Or(&mockFilter{r: false}, &mockFilter{r: true}), true},
		{"or3", Or(&mockFilter{r: true}, &mockFilter{r: false}), true},
		{"or4", Or(&mockFilter{r: true}, &mockFilter{r: true}), true},

		{"not1", Not(&mockFilter{r: true}), false},
		{"not2", Not(&mockFilter{r: false}), true},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			match, err := tc.filter.Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expected {
				t.Fatalf("expected %v, but got %v", tc.expected, match)
			}
		})
	}
}

type mockFilter struct {
	r   bool
	n   string
	buf *bytes.Buffer
}

func (m *mockFilter) Match(fields Fields) (bool, error) {
	if m.buf != nil {
		m.buf.Write([]byte(m.n))
	}
	return m.r, nil
}

func TestFilters(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName string
		filter   Filter
		fields   Fields
		expected bool
	}{
		{"field exist", FieldExist("time"), Fields{"time": 123}, true},
		{"field not exist", FieldExist("time2"), Fields{"time": 123}, false},
		{"field exist dotpath", FieldExist("user.id"), Fields{"user": Fields{"id": 1}}, true},
		{"field not exist dotpath", FieldExist("user.username"), Fields{"user": Fields{"id": 1}}, false},

		{"eq string", Eq("logger", "requestLogger"), Fields{"logger": "requestLogger"}, true},
		{"not eq string", Eq("logger", "requestLogger2"), Fields{"logger": "requestLogger"}, false},
		{"eq string dotpath", Eq("user.id", 1), Fields{"user": Fields{"id": 1}}, true},
		{"not eq string dotpath", Eq("user.id", 2), Fields{"user": Fields{"id": 1}}, false},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			match, err := tc.filter.Match(tc.fields)
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expected {
				t.Fatalf("expected %v, but got %v", tc.expected, match)
			}
		})
	}
}

func TestAndShortCircuit(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName      string
		filters       []Filter
		expectedMatch bool
		expectedOrder string
	}{
		{"and1", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "A"},
		{"and2", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: true, n: "B"}}, false, "A"},
		{"and3", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "AB"},
		{"and4", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "AB"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			buf := &bytes.Buffer{}
			for _, f := range tc.filters {
				(f.(*mockFilter)).buf = buf
			}

			match, err := And(tc.filters...).Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expectedMatch {
				t.Fatalf("expected %v, but got %v", tc.expectedMatch, match)
			}
			if buf.String() != tc.expectedOrder {
				t.Fatalf("expected %v, but got %v", tc.expectedOrder, buf.String())
			}
		})
	}
}

func TestOrShortCircuit(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		testName      string
		filters       []Filter
		expectedMatch bool
		expectedOrder string
	}{
		{"or1", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: false, n: "B"}}, false, "AB"},
		{"or2", []Filter{&mockFilter{r: false, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "AB"},
		{"or3", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: false, n: "B"}}, true, "A"},
		{"or4", []Filter{&mockFilter{r: true, n: "A"}, &mockFilter{r: true, n: "B"}}, true, "A"},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.testName, func(t *testing.T) {
			t.Parallel()
			buf := &bytes.Buffer{}
			for _, f := range tc.filters {
				(f.(*mockFilter)).buf = buf
			}

			match, err := Or(tc.filters...).Match(Fields{})
			if err != nil {
				t.Fatalf("non nil error: %v", err)
			}
			if match != tc.expectedMatch {
				t.Fatalf("expected %v, but got %v", tc.expectedMatch, match)
			}
			if buf.String() != tc.expectedOrder {
				t.Fatalf("expected %v, but got %v", tc.expectedOrder, buf.String())
			}
		})
	}
}
<|endoftext|>