add prune and remove unused packages

This commit is contained in:
Michelle Au
2019-03-08 14:54:43 -08:00
parent f59b58d164
commit 8c0accad66
17240 changed files with 27 additions and 4750030 deletions

View File

@@ -1,141 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
// Package binarylog implementation binary logging as defined in
// https://github.com/grpc/proposal/blob/master/A16-binary-logging.md.
package binarylog
import (
"fmt"
"os"
"google.golang.org/grpc/grpclog"
)
// Logger is the global binary logger for the binary. One of this should be
// built at init time from the configuration (environment varialbe or flags).
//
// It is used to get a methodLogger for each individual method.
var Logger *logger
func init() {
const envStr = "GRPC_BINARY_LOG_FILTER"
configStr := os.Getenv(envStr)
Logger = newLoggerFromConfigString(configStr)
}
type methodLoggerConfig struct {
// Max length of header and message.
hdr, msg uint64
}
type logger struct {
all *methodLoggerConfig
services map[string]*methodLoggerConfig
methods map[string]*methodLoggerConfig
blacklist map[string]struct{}
}
// newEmptyLogger creates an empty logger. The map fields need to be filled in
// using the set* functions.
func newEmptyLogger() *logger {
return &logger{}
}
// Set method logger for "*".
func (l *logger) setDefaultMethodLogger(ml *methodLoggerConfig) error {
if l.all != nil {
return fmt.Errorf("conflicting global rules found")
}
l.all = ml
return nil
}
// Set method logger for "service/*".
//
// New methodLogger with same service overrides the old one.
func (l *logger) setServiceMethodLogger(service string, ml *methodLoggerConfig) error {
if _, ok := l.services[service]; ok {
return fmt.Errorf("conflicting rules for service %v found", service)
}
if l.services == nil {
l.services = make(map[string]*methodLoggerConfig)
}
l.services[service] = ml
return nil
}
// Set method logger for "service/method".
//
// New methodLogger with same method overrides the old one.
func (l *logger) setMethodMethodLogger(method string, ml *methodLoggerConfig) error {
if _, ok := l.blacklist[method]; ok {
return fmt.Errorf("conflicting rules for method %v found", method)
}
if _, ok := l.methods[method]; ok {
return fmt.Errorf("conflicting rules for method %v found", method)
}
if l.methods == nil {
l.methods = make(map[string]*methodLoggerConfig)
}
l.methods[method] = ml
return nil
}
// Set blacklist method for "-service/method".
func (l *logger) setBlacklist(method string) error {
if _, ok := l.blacklist[method]; ok {
return fmt.Errorf("conflicting rules for method %v found", method)
}
if _, ok := l.methods[method]; ok {
return fmt.Errorf("conflicting rules for method %v found", method)
}
if l.blacklist == nil {
l.blacklist = make(map[string]struct{})
}
l.blacklist[method] = struct{}{}
return nil
}
// GetMethodLogger returns the methodLogger for the given methodName.
//
// methodName should be in the format of "/service/method".
//
// Each methodLogger returned by this method is a new instance. This is to
// generate sequence id within the call.
func (l *logger) GetMethodLogger(methodName string) *MethodLogger {
s, m, err := parseMethodName(methodName)
if err != nil {
grpclog.Infof("binarylogging: failed to parse %q: %v", methodName, err)
return nil
}
if ml, ok := l.methods[s+"/"+m]; ok {
return newMethodLogger(ml.hdr, ml.msg)
}
if _, ok := l.blacklist[s+"/"+m]; ok {
return nil
}
if ml, ok := l.services[s]; ok {
return newMethodLogger(ml.hdr, ml.msg)
}
if l.all == nil {
return nil
}
return newMethodLogger(l.all.hdr, l.all.msg)
}

View File

@@ -1,147 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"testing"
)
// Test that get method logger returns the one with the most exact match.
func TestGetMethodLogger(t *testing.T) {
testCases := []struct {
in string
method string
hdr, msg uint64
}{
// Global.
{
in: "*{h:12;m:23}",
method: "/s/m",
hdr: 12, msg: 23,
},
// service/*.
{
in: "*,s/*{h:12;m:23}",
method: "/s/m",
hdr: 12, msg: 23,
},
// Service/method.
{
in: "*{h;m},s/m{h:12;m:23}",
method: "/s/m",
hdr: 12, msg: 23,
},
{
in: "*{h;m},s/*{h:314;m},s/m{h:12;m:23}",
method: "/s/m",
hdr: 12, msg: 23,
},
{
in: "*{h;m},s/*{h:12;m:23},s/m",
method: "/s/m",
hdr: maxUInt, msg: maxUInt,
},
// service/*.
{
in: "*{h;m},s/*{h:12;m:23},s/m1",
method: "/s/m",
hdr: 12, msg: 23,
},
{
in: "*{h;m},s1/*,s/m{h:12;m:23}",
method: "/s/m",
hdr: 12, msg: 23,
},
// With black list.
{
in: "*{h:12;m:23},-s/m1",
method: "/s/m",
hdr: 12, msg: 23,
},
}
for _, tc := range testCases {
l := newLoggerFromConfigString(tc.in)
if l == nil {
t.Errorf("in: %q, failed to create logger from config string", tc.in)
continue
}
ml := l.GetMethodLogger(tc.method)
if ml == nil {
t.Errorf("in: %q, method logger is nil, want non-nil", tc.in)
continue
}
if ml.headerMaxLen != tc.hdr || ml.messageMaxLen != tc.msg {
t.Errorf("in: %q, want header: %v, message: %v, got header: %v, message: %v", tc.in, tc.hdr, tc.msg, ml.headerMaxLen, ml.messageMaxLen)
}
}
}
// expect method logger to be nil
func TestGetMethodLoggerOff(t *testing.T) {
testCases := []struct {
in string
method string
}{
// method not specified.
{
in: "s1/m",
method: "/s/m",
},
{
in: "s/m1",
method: "/s/m",
},
{
in: "s1/*",
method: "/s/m",
},
{
in: "s1/*,s/m1",
method: "/s/m",
},
// blacklisted.
{
in: "*,-s/m",
method: "/s/m",
},
{
in: "s/*,-s/m",
method: "/s/m",
},
{
in: "-s/m,s/*",
method: "/s/m",
},
}
for _, tc := range testCases {
l := newLoggerFromConfigString(tc.in)
if l == nil {
t.Errorf("in: %q, failed to create logger from config string", tc.in)
continue
}
ml := l.GetMethodLogger(tc.method)
if ml != nil {
t.Errorf("in: %q, method logger is non-nil, want nil", tc.in)
}
}
}

View File

@@ -1,206 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"google.golang.org/grpc/grpclog"
)
// newLoggerFromConfigString reads the string and build a logger.
//
// Example filter config strings:
// - "" Nothing will be logged
// - "*" All headers and messages will be fully logged.
// - "*{h}" Only headers will be logged.
// - "*{m:256}" Only the first 256 bytes of each message will be logged.
// - "Foo/*" Logs every method in service Foo
// - "Foo/*,-Foo/Bar" Logs every method in service Foo except method /Foo/Bar
// - "Foo/*,Foo/Bar{m:256}" Logs the first 256 bytes of each message in method
// /Foo/Bar, logs all headers and messages in every other method in service
// Foo.
//
// If two configs exist for one certain method or service, the one specified
// later overrides the privous config.
func newLoggerFromConfigString(s string) *logger {
l := newEmptyLogger()
methods := strings.Split(s, ",")
for _, method := range methods {
if err := l.fillMethodLoggerWithConfigString(method); err != nil {
grpclog.Warningf("failed to parse binary log config: %v", err)
return nil
}
}
return l
}
// fillMethodLoggerWithConfigString parses config, creates methodLogger and adds
// it to the right map in the logger.
func (l *logger) fillMethodLoggerWithConfigString(config string) error {
// "" is invalid.
if config == "" {
return errors.New("empty string is not a valid method binary logging config")
}
// "-service/method", blacklist, no * or {} allowed.
if config[0] == '-' {
s, m, suffix, err := parseMethodConfigAndSuffix(config[1:])
if err != nil {
return fmt.Errorf("invalid config: %q, %v", config, err)
}
if m == "*" {
return fmt.Errorf("invalid config: %q, %v", config, "* not allowd in blacklist config")
}
if suffix != "" {
return fmt.Errorf("invalid config: %q, %v", config, "header/message limit not allowed in blacklist config")
}
if err := l.setBlacklist(s + "/" + m); err != nil {
return fmt.Errorf("invalid config: %v", err)
}
return nil
}
// "*{h:256;m:256}"
if config[0] == '*' {
hdr, msg, err := parseHeaderMessageLengthConfig(config[1:])
if err != nil {
return fmt.Errorf("invalid config: %q, %v", config, err)
}
if err := l.setDefaultMethodLogger(&methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
return fmt.Errorf("invalid config: %v", err)
}
return nil
}
s, m, suffix, err := parseMethodConfigAndSuffix(config)
if err != nil {
return fmt.Errorf("invalid config: %q, %v", config, err)
}
hdr, msg, err := parseHeaderMessageLengthConfig(suffix)
if err != nil {
return fmt.Errorf("invalid header/message length config: %q, %v", suffix, err)
}
if m == "*" {
if err := l.setServiceMethodLogger(s, &methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
return fmt.Errorf("invalid config: %v", err)
}
} else {
if err := l.setMethodMethodLogger(s+"/"+m, &methodLoggerConfig{hdr: hdr, msg: msg}); err != nil {
return fmt.Errorf("invalid config: %v", err)
}
}
return nil
}
const (
// TODO: this const is only used by env_config now. But could be useful for
// other config. Move to binarylog.go if necessary.
maxUInt = ^uint64(0)
// For "p.s/m" plus any suffix. Suffix will be parsed again. See test for
// expected output.
longMethodConfigRegexpStr = `^([\w./]+)/((?:\w+)|[*])(.+)?$`
// For suffix from above, "{h:123,m:123}". See test for expected output.
optionalLengthRegexpStr = `(?::(\d+))?` // Optional ":123".
headerConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `}$`
messageConfigRegexpStr = `^{m` + optionalLengthRegexpStr + `}$`
headerMessageConfigRegexpStr = `^{h` + optionalLengthRegexpStr + `;m` + optionalLengthRegexpStr + `}$`
)
var (
longMethodConfigRegexp = regexp.MustCompile(longMethodConfigRegexpStr)
headerConfigRegexp = regexp.MustCompile(headerConfigRegexpStr)
messageConfigRegexp = regexp.MustCompile(messageConfigRegexpStr)
headerMessageConfigRegexp = regexp.MustCompile(headerMessageConfigRegexpStr)
)
// Turn "service/method{h;m}" into "service", "method", "{h;m}".
func parseMethodConfigAndSuffix(c string) (service, method, suffix string, _ error) {
// Regexp result:
//
// in: "p.s/m{h:123,m:123}",
// out: []string{"p.s/m{h:123,m:123}", "p.s", "m", "{h:123,m:123}"},
match := longMethodConfigRegexp.FindStringSubmatch(c)
if match == nil {
return "", "", "", fmt.Errorf("%q contains invalid substring", c)
}
service = match[1]
method = match[2]
suffix = match[3]
return
}
// Turn "{h:123;m:345}" into 123, 345.
//
// Return maxUInt if length is unspecified.
func parseHeaderMessageLengthConfig(c string) (hdrLenStr, msgLenStr uint64, err error) {
if c == "" {
return maxUInt, maxUInt, nil
}
// Header config only.
if match := headerConfigRegexp.FindStringSubmatch(c); match != nil {
if s := match[1]; s != "" {
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("failed to convert %q to uint", s)
}
return hdrLenStr, 0, nil
}
return maxUInt, 0, nil
}
// Message config only.
if match := messageConfigRegexp.FindStringSubmatch(c); match != nil {
if s := match[1]; s != "" {
msgLenStr, err = strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
}
return 0, msgLenStr, nil
}
return 0, maxUInt, nil
}
// Header and message config both.
if match := headerMessageConfigRegexp.FindStringSubmatch(c); match != nil {
// Both hdr and msg are specified, but one or two of them might be empty.
hdrLenStr = maxUInt
msgLenStr = maxUInt
if s := match[1]; s != "" {
hdrLenStr, err = strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
}
}
if s := match[2]; s != "" {
msgLenStr, err = strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("Failed to convert %q to uint", s)
}
}
return hdrLenStr, msgLenStr, nil
}
return 0, 0, fmt.Errorf("%q contains invalid substring", c)
}

View File

@@ -1,478 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"fmt"
"testing"
)
// This tests that when multiple configs are specified, all methods loggers will
// be set correctly. Correctness of each logger is covered by other unit tests.
func TestNewLoggerFromConfigString(t *testing.T) {
const (
s1 = "s1"
m1 = "m1"
m2 = "m2"
fullM1 = s1 + "/" + m1
fullM2 = s1 + "/" + m2
)
c := fmt.Sprintf("*{h:1;m:2},%s{h},%s{m},%s{h;m}", s1+"/*", fullM1, fullM2)
l := newLoggerFromConfigString(c)
if l.all.hdr != 1 || l.all.msg != 2 {
t.Errorf("l.all = %#v, want headerLen: 1, messageLen: 2", l.all)
}
if ml, ok := l.services[s1]; ok {
if ml.hdr != maxUInt || ml.msg != 0 {
t.Errorf("want maxUInt header, 0 message, got header: %v, message: %v", ml.hdr, ml.msg)
}
} else {
t.Errorf("service/* is not set")
}
if ml, ok := l.methods[fullM1]; ok {
if ml.hdr != 0 || ml.msg != maxUInt {
t.Errorf("want 0 header, maxUInt message, got header: %v, message: %v", ml.hdr, ml.msg)
}
} else {
t.Errorf("service/method{h} is not set")
}
if ml, ok := l.methods[fullM2]; ok {
if ml.hdr != maxUInt || ml.msg != maxUInt {
t.Errorf("want maxUInt header, maxUInt message, got header: %v, message: %v", ml.hdr, ml.msg)
}
} else {
t.Errorf("service/method{h;m} is not set")
}
}
func TestNewLoggerFromConfigStringInvalid(t *testing.T) {
testCases := []string{
"",
"*{}",
"s/m,*{}",
"s/m,s/m{a}",
// Duplciate rules.
"s/m,-s/m",
"-s/m,s/m",
"s/m,s/m",
"s/m,s/m{h:1;m:1}",
"s/m{h:1;m:1},s/m",
"-s/m,-s/m",
"s/*,s/*{h:1;m:1}",
"*,*{h:1;m:1}",
}
for _, tc := range testCases {
l := newLoggerFromConfigString(tc)
if l != nil {
t.Errorf("With config %q, want logger %v, got %v", tc, nil, l)
}
}
}
func TestParseMethodConfigAndSuffix(t *testing.T) {
testCases := []struct {
in, service, method, suffix string
}{
{
in: "p.s/m",
service: "p.s", method: "m", suffix: "",
},
{
in: "p.s/m{h,m}",
service: "p.s", method: "m", suffix: "{h,m}",
},
{
in: "p.s/*",
service: "p.s", method: "*", suffix: "",
},
{
in: "p.s/*{h,m}",
service: "p.s", method: "*", suffix: "{h,m}",
},
// invalid suffix will be detected by another function.
{
in: "p.s/m{invalidsuffix}",
service: "p.s", method: "m", suffix: "{invalidsuffix}",
},
{
in: "p.s/*{invalidsuffix}",
service: "p.s", method: "*", suffix: "{invalidsuffix}",
},
{
in: "s/m*",
service: "s", method: "m", suffix: "*",
},
{
in: "s/*m",
service: "s", method: "*", suffix: "m",
},
{
in: "s/**",
service: "s", method: "*", suffix: "*",
},
}
for _, tc := range testCases {
t.Logf("testing parseMethodConfigAndSuffix(%q)", tc.in)
s, m, suffix, err := parseMethodConfigAndSuffix(tc.in)
if err != nil {
t.Errorf("returned error %v, want nil", err)
continue
}
if s != tc.service {
t.Errorf("service = %q, want %q", s, tc.service)
}
if m != tc.method {
t.Errorf("method = %q, want %q", m, tc.method)
}
if suffix != tc.suffix {
t.Errorf("suffix = %q, want %q", suffix, tc.suffix)
}
}
}
func TestParseMethodConfigAndSuffixInvalid(t *testing.T) {
testCases := []string{
"*/m",
"*/m{}",
}
for _, tc := range testCases {
s, m, suffix, err := parseMethodConfigAndSuffix(tc)
if err == nil {
t.Errorf("Parsing %q got nil error with %q, %q, %q, want non-nil error", tc, s, m, suffix)
}
}
}
func TestParseHeaderMessageLengthConfig(t *testing.T) {
testCases := []struct {
in string
hdr, msg uint64
}{
{
in: "",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h}",
hdr: maxUInt, msg: 0,
},
{
in: "{h:314}",
hdr: 314, msg: 0,
},
{
in: "{m}",
hdr: 0, msg: maxUInt,
},
{
in: "{m:213}",
hdr: 0, msg: 213,
},
{
in: "{h;m}",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h:314;m}",
hdr: 314, msg: maxUInt,
},
{
in: "{h;m:213}",
hdr: maxUInt, msg: 213,
},
{
in: "{h:314;m:213}",
hdr: 314, msg: 213,
},
}
for _, tc := range testCases {
t.Logf("testing parseHeaderMessageLengthConfig(%q)", tc.in)
hdr, msg, err := parseHeaderMessageLengthConfig(tc.in)
if err != nil {
t.Errorf("returned error %v, want nil", err)
continue
}
if hdr != tc.hdr {
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
}
if msg != tc.msg {
t.Errorf("message length = %v, want %v", msg, tc.msg)
}
}
}
func TestParseHeaderMessageLengthConfigInvalid(t *testing.T) {
testCases := []string{
"{}",
"{h;a}",
"{h;m;b}",
}
for _, tc := range testCases {
_, _, err := parseHeaderMessageLengthConfig(tc)
if err == nil {
t.Errorf("Parsing %q got nil error, want non-nil error", tc)
}
}
}
func TestFillMethodLoggerWithConfigStringBlacklist(t *testing.T) {
testCases := []string{
"p.s/m",
"service/method",
}
for _, tc := range testCases {
c := "-" + tc
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
l := newEmptyLogger()
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
t.Errorf("returned err %v, want nil", err)
continue
}
_, ok := l.blacklist[tc]
if !ok {
t.Errorf("blacklist[%q] is not set", tc)
}
}
}
func TestFillMethodLoggerWithConfigStringGlobal(t *testing.T) {
testCases := []struct {
in string
hdr, msg uint64
}{
{
in: "",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h}",
hdr: maxUInt, msg: 0,
},
{
in: "{h:314}",
hdr: 314, msg: 0,
},
{
in: "{m}",
hdr: 0, msg: maxUInt,
},
{
in: "{m:213}",
hdr: 0, msg: 213,
},
{
in: "{h;m}",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h:314;m}",
hdr: 314, msg: maxUInt,
},
{
in: "{h;m:213}",
hdr: maxUInt, msg: 213,
},
{
in: "{h:314;m:213}",
hdr: 314, msg: 213,
},
}
for _, tc := range testCases {
c := "*" + tc.in
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
l := newEmptyLogger()
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
t.Errorf("returned err %v, want nil", err)
continue
}
if l.all == nil {
t.Errorf("l.all is not set")
continue
}
if hdr := l.all.hdr; hdr != tc.hdr {
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
}
if msg := l.all.msg; msg != tc.msg {
t.Errorf("message length = %v, want %v", msg, tc.msg)
}
}
}
func TestFillMethodLoggerWithConfigStringPerService(t *testing.T) {
testCases := []struct {
in string
hdr, msg uint64
}{
{
in: "",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h}",
hdr: maxUInt, msg: 0,
},
{
in: "{h:314}",
hdr: 314, msg: 0,
},
{
in: "{m}",
hdr: 0, msg: maxUInt,
},
{
in: "{m:213}",
hdr: 0, msg: 213,
},
{
in: "{h;m}",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h:314;m}",
hdr: 314, msg: maxUInt,
},
{
in: "{h;m:213}",
hdr: maxUInt, msg: 213,
},
{
in: "{h:314;m:213}",
hdr: 314, msg: 213,
},
}
const serviceName = "service"
for _, tc := range testCases {
c := serviceName + "/*" + tc.in
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
l := newEmptyLogger()
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
t.Errorf("returned err %v, want nil", err)
continue
}
ml, ok := l.services[serviceName]
if !ok {
t.Errorf("l.service[%q] is not set", serviceName)
continue
}
if hdr := ml.hdr; hdr != tc.hdr {
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
}
if msg := ml.msg; msg != tc.msg {
t.Errorf("message length = %v, want %v", msg, tc.msg)
}
}
}
func TestFillMethodLoggerWithConfigStringPerMethod(t *testing.T) {
testCases := []struct {
in string
hdr, msg uint64
}{
{
in: "",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h}",
hdr: maxUInt, msg: 0,
},
{
in: "{h:314}",
hdr: 314, msg: 0,
},
{
in: "{m}",
hdr: 0, msg: maxUInt,
},
{
in: "{m:213}",
hdr: 0, msg: 213,
},
{
in: "{h;m}",
hdr: maxUInt, msg: maxUInt,
},
{
in: "{h:314;m}",
hdr: 314, msg: maxUInt,
},
{
in: "{h;m:213}",
hdr: maxUInt, msg: 213,
},
{
in: "{h:314;m:213}",
hdr: 314, msg: 213,
},
}
const (
serviceName = "service"
methodName = "method"
fullMethodName = serviceName + "/" + methodName
)
for _, tc := range testCases {
c := fullMethodName + tc.in
t.Logf("testing fillMethodLoggerWithConfigString(%q)", c)
l := newEmptyLogger()
if err := l.fillMethodLoggerWithConfigString(c); err != nil {
t.Errorf("returned err %v, want nil", err)
continue
}
ml, ok := l.methods[fullMethodName]
if !ok {
t.Errorf("l.methods[%q] is not set", fullMethodName)
continue
}
if hdr := ml.hdr; hdr != tc.hdr {
t.Errorf("header length = %v, want %v", hdr, tc.hdr)
}
if msg := ml.msg; msg != tc.msg {
t.Errorf("message length = %v, want %v", msg, tc.msg)
}
}
}
func TestFillMethodLoggerWithConfigStringInvalid(t *testing.T) {
testCases := []string{
"",
"{}",
"p.s/m{}",
"p.s/m{a}",
"p.s/m*",
"p.s/**",
"*/m",
"-p.s/*",
"-p.s/m{h}",
}
l := &logger{}
for _, tc := range testCases {
if err := l.fillMethodLoggerWithConfigString(tc); err == nil {
t.Errorf("fillMethodLoggerWithConfigString(%q) returned nil error, want non-nil", tc)
}
}
}

View File

@@ -1,426 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"net"
"strings"
"sync/atomic"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type callIDGenerator struct {
id uint64
}
func (g *callIDGenerator) next() uint64 {
id := atomic.AddUint64(&g.id, 1)
return id
}
// reset is for testing only, and doesn't need to be thread safe.
func (g *callIDGenerator) reset() {
g.id = 0
}
var idGen callIDGenerator
// MethodLogger is the sub-logger for each method.
type MethodLogger struct {
headerMaxLen, messageMaxLen uint64
callID uint64
idWithinCallGen *callIDGenerator
sink Sink // TODO(blog): make this plugable.
}
func newMethodLogger(h, m uint64) *MethodLogger {
return &MethodLogger{
headerMaxLen: h,
messageMaxLen: m,
callID: idGen.next(),
idWithinCallGen: &callIDGenerator{},
sink: defaultSink, // TODO(blog): make it plugable.
}
}
// Log creates a proto binary log entry, and logs it to the sink.
func (ml *MethodLogger) Log(c LogEntryConfig) {
m := c.toProto()
timestamp, _ := ptypes.TimestampProto(time.Now())
m.Timestamp = timestamp
m.CallId = ml.callID
m.SequenceIdWithinCall = ml.idWithinCallGen.next()
switch pay := m.Payload.(type) {
case *pb.GrpcLogEntry_ClientHeader:
m.PayloadTruncated = ml.truncateMetadata(pay.ClientHeader.GetMetadata())
case *pb.GrpcLogEntry_ServerHeader:
m.PayloadTruncated = ml.truncateMetadata(pay.ServerHeader.GetMetadata())
case *pb.GrpcLogEntry_Message:
m.PayloadTruncated = ml.truncateMessage(pay.Message)
}
ml.sink.Write(m)
}
func (ml *MethodLogger) truncateMetadata(mdPb *pb.Metadata) (truncated bool) {
if ml.headerMaxLen == maxUInt {
return false
}
var (
bytesLimit = ml.headerMaxLen
index int
)
// At the end of the loop, index will be the first entry where the total
// size is greater than the limit:
//
// len(entry[:index]) <= ml.hdr && len(entry[:index+1]) > ml.hdr.
for ; index < len(mdPb.Entry); index++ {
entry := mdPb.Entry[index]
if entry.Key == "grpc-trace-bin" {
// "grpc-trace-bin" is a special key. It's kept in the log entry,
// but not counted towards the size limit.
continue
}
currentEntryLen := uint64(len(entry.Value))
if currentEntryLen > bytesLimit {
break
}
bytesLimit -= currentEntryLen
}
truncated = index < len(mdPb.Entry)
mdPb.Entry = mdPb.Entry[:index]
return truncated
}
func (ml *MethodLogger) truncateMessage(msgPb *pb.Message) (truncated bool) {
if ml.messageMaxLen == maxUInt {
return false
}
if ml.messageMaxLen >= uint64(len(msgPb.Data)) {
return false
}
msgPb.Data = msgPb.Data[:ml.messageMaxLen]
return true
}
// LogEntryConfig represents the configuration for binary log entry.
type LogEntryConfig interface {
toProto() *pb.GrpcLogEntry
}
// ClientHeader configs the binary log entry to be a ClientHeader entry.
type ClientHeader struct {
OnClientSide bool
Header metadata.MD
MethodName string
Authority string
Timeout time.Duration
// PeerAddr is required only when it's on server side.
PeerAddr net.Addr
}
func (c *ClientHeader) toProto() *pb.GrpcLogEntry {
// This function doesn't need to set all the fields (e.g. seq ID). The Log
// function will set the fields when necessary.
clientHeader := &pb.ClientHeader{
Metadata: mdToMetadataProto(c.Header),
MethodName: c.MethodName,
Authority: c.Authority,
}
if c.Timeout > 0 {
clientHeader.Timeout = ptypes.DurationProto(c.Timeout)
}
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
Payload: &pb.GrpcLogEntry_ClientHeader{
ClientHeader: clientHeader,
},
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
if c.PeerAddr != nil {
ret.Peer = addrToProto(c.PeerAddr)
}
return ret
}
// ServerHeader configs the binary log entry to be a ServerHeader entry.
type ServerHeader struct {
OnClientSide bool
Header metadata.MD
// PeerAddr is required only when it's on client side.
PeerAddr net.Addr
}
func (c *ServerHeader) toProto() *pb.GrpcLogEntry {
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
Payload: &pb.GrpcLogEntry_ServerHeader{
ServerHeader: &pb.ServerHeader{
Metadata: mdToMetadataProto(c.Header),
},
},
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
if c.PeerAddr != nil {
ret.Peer = addrToProto(c.PeerAddr)
}
return ret
}
// ClientMessage configs the binary log entry to be a ClientMessage entry.
type ClientMessage struct {
OnClientSide bool
// Message should only be a proto.Message. Could add support for other
// message types in the future.
Message interface{}
}
func (c *ClientMessage) toProto() *pb.GrpcLogEntry {
var (
data []byte
err error
)
if m, ok := c.Message.(proto.Message); ok {
data, err = proto.Marshal(m)
if err != nil {
grpclog.Infof("binarylogging: failed to marshal proto message: %v", err)
}
} else if b, ok := c.Message.([]byte); ok {
data = b
} else {
grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte")
}
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
Payload: &pb.GrpcLogEntry_Message{
Message: &pb.Message{
Length: uint32(len(data)),
Data: data,
},
},
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
return ret
}
// ServerMessage configs the binary log entry to be a ServerMessage entry.
type ServerMessage struct {
OnClientSide bool
// Message should only be a proto.Message. Could add support for other
// message types in the future.
Message interface{}
}
func (c *ServerMessage) toProto() *pb.GrpcLogEntry {
var (
data []byte
err error
)
if m, ok := c.Message.(proto.Message); ok {
data, err = proto.Marshal(m)
if err != nil {
grpclog.Infof("binarylogging: failed to marshal proto message: %v", err)
}
} else if b, ok := c.Message.([]byte); ok {
data = b
} else {
grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte")
}
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
Payload: &pb.GrpcLogEntry_Message{
Message: &pb.Message{
Length: uint32(len(data)),
Data: data,
},
},
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
return ret
}
// ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
type ClientHalfClose struct {
OnClientSide bool
}
func (c *ClientHalfClose) toProto() *pb.GrpcLogEntry {
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
Payload: nil, // No payload here.
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
return ret
}
// ServerTrailer configs the binary log entry to be a ServerTrailer entry.
type ServerTrailer struct {
OnClientSide bool
Trailer metadata.MD
// Err is the status error.
Err error
// PeerAddr is required only when it's on client side and the RPC is trailer
// only.
PeerAddr net.Addr
}
func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
st, ok := status.FromError(c.Err)
if !ok {
grpclog.Info("binarylogging: error in trailer is not a status error")
}
var (
detailsBytes []byte
err error
)
stProto := st.Proto()
if stProto != nil && len(stProto.Details) != 0 {
detailsBytes, err = proto.Marshal(stProto)
if err != nil {
grpclog.Infof("binarylogging: failed to marshal status proto: %v", err)
}
}
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
Payload: &pb.GrpcLogEntry_Trailer{
Trailer: &pb.Trailer{
Metadata: mdToMetadataProto(c.Trailer),
StatusCode: uint32(st.Code()),
StatusMessage: st.Message(),
StatusDetails: detailsBytes,
},
},
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
if c.PeerAddr != nil {
ret.Peer = addrToProto(c.PeerAddr)
}
return ret
}
// Cancel configs the binary log entry to be a Cancel entry.
type Cancel struct {
OnClientSide bool
}
func (c *Cancel) toProto() *pb.GrpcLogEntry {
ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_CANCEL,
Payload: nil,
}
if c.OnClientSide {
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
} else {
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
}
return ret
}
// metadataKeyOmit returns whether the metadata entry with this key should be
// omitted.
func metadataKeyOmit(key string) bool {
switch key {
case "lb-token", ":path", ":authority", "content-encoding", "user-agent", "te":
return true
case "grpc-trace-bin": // grpc-trace-bin is special because it's visiable to users.
return false
}
if strings.HasPrefix(key, "grpc-") {
return true
}
return false
}
func mdToMetadataProto(md metadata.MD) *pb.Metadata {
ret := &pb.Metadata{}
for k, vv := range md {
if metadataKeyOmit(k) {
continue
}
for _, v := range vv {
ret.Entry = append(ret.Entry,
&pb.MetadataEntry{
Key: k,
Value: []byte(v),
},
)
}
}
return ret
}
func addrToProto(addr net.Addr) *pb.Address {
ret := &pb.Address{}
switch a := addr.(type) {
case *net.TCPAddr:
if a.IP.To4() != nil {
ret.Type = pb.Address_TYPE_IPV4
} else if a.IP.To16() != nil {
ret.Type = pb.Address_TYPE_IPV6
} else {
ret.Type = pb.Address_TYPE_UNKNOWN
// Do not set address and port fields.
break
}
ret.Address = a.IP.String()
ret.IpPort = uint32(a.Port)
case *net.UnixAddr:
ret.Type = pb.Address_TYPE_UNIX
ret.Address = a.String()
default:
ret.Type = pb.Address_TYPE_UNKNOWN
}
return ret
}

View File

@@ -1,542 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"bytes"
"fmt"
"net"
"testing"
"time"
"github.com/golang/protobuf/proto"
dpb "github.com/golang/protobuf/ptypes/duration"
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestLog(t *testing.T) {
idGen.reset()
ml := newMethodLogger(10, 10)
// Set sink to testing buffer.
buf := bytes.NewBuffer(nil)
ml.sink = NewWriterSink(buf)
addr := "1.2.3.4"
port := 790
tcpAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("%v:%d", addr, port))
addr6 := "2001:1db8:85a3::8a2e:1370:7334"
port6 := 796
tcpAddr6, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("[%v]:%d", addr6, port6))
testProtoMsg := &pb.Message{
Length: 1,
Data: []byte{'a'},
}
testProtoBytes, _ := proto.Marshal(testProtoMsg)
testCases := []struct {
config LogEntryConfig
want *pb.GrpcLogEntry
}{
{
config: &ClientHeader{
OnClientSide: false,
Header: map[string][]string{
"a": {"b", "bb"},
},
MethodName: "testservice/testmethod",
Authority: "test.service.io",
Timeout: 2*time.Second + 3*time.Nanosecond,
PeerAddr: tcpAddr,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
Logger: pb.GrpcLogEntry_LOGGER_SERVER,
Payload: &pb.GrpcLogEntry_ClientHeader{
ClientHeader: &pb.ClientHeader{
Metadata: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "a", Value: []byte{'b'}},
{Key: "a", Value: []byte{'b', 'b'}},
},
},
MethodName: "testservice/testmethod",
Authority: "test.service.io",
Timeout: &dpb.Duration{
Seconds: 2,
Nanos: 3,
},
},
},
PayloadTruncated: false,
Peer: &pb.Address{
Type: pb.Address_TYPE_IPV4,
Address: addr,
IpPort: uint32(port),
},
},
},
{
config: &ClientHeader{
OnClientSide: false,
MethodName: "testservice/testmethod",
Authority: "test.service.io",
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
Logger: pb.GrpcLogEntry_LOGGER_SERVER,
Payload: &pb.GrpcLogEntry_ClientHeader{
ClientHeader: &pb.ClientHeader{
Metadata: &pb.Metadata{},
MethodName: "testservice/testmethod",
Authority: "test.service.io",
},
},
PayloadTruncated: false,
},
},
{
config: &ServerHeader{
OnClientSide: true,
Header: map[string][]string{
"a": {"b", "bb"},
},
PeerAddr: tcpAddr6,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: &pb.GrpcLogEntry_ServerHeader{
ServerHeader: &pb.ServerHeader{
Metadata: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "a", Value: []byte{'b'}},
{Key: "a", Value: []byte{'b', 'b'}},
},
},
},
},
PayloadTruncated: false,
Peer: &pb.Address{
Type: pb.Address_TYPE_IPV6,
Address: addr6,
IpPort: uint32(port6),
},
},
},
{
config: &ClientMessage{
OnClientSide: true,
Message: testProtoMsg,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: &pb.GrpcLogEntry_Message{
Message: &pb.Message{
Length: uint32(len(testProtoBytes)),
Data: testProtoBytes,
},
},
PayloadTruncated: false,
Peer: nil,
},
},
{
config: &ServerMessage{
OnClientSide: false,
Message: testProtoMsg,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
Logger: pb.GrpcLogEntry_LOGGER_SERVER,
Payload: &pb.GrpcLogEntry_Message{
Message: &pb.Message{
Length: uint32(len(testProtoBytes)),
Data: testProtoBytes,
},
},
PayloadTruncated: false,
Peer: nil,
},
},
{
config: &ClientHalfClose{
OnClientSide: false,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
Logger: pb.GrpcLogEntry_LOGGER_SERVER,
Payload: nil,
PayloadTruncated: false,
Peer: nil,
},
},
{
config: &ServerTrailer{
OnClientSide: true,
Err: status.Errorf(codes.Unavailable, "test"),
PeerAddr: tcpAddr,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: &pb.GrpcLogEntry_Trailer{
Trailer: &pb.Trailer{
Metadata: &pb.Metadata{},
StatusCode: uint32(codes.Unavailable),
StatusMessage: "test",
StatusDetails: nil,
},
},
PayloadTruncated: false,
Peer: &pb.Address{
Type: pb.Address_TYPE_IPV4,
Address: addr,
IpPort: uint32(port),
},
},
},
{ // Err is nil, Log OK status.
config: &ServerTrailer{
OnClientSide: true,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: &pb.GrpcLogEntry_Trailer{
Trailer: &pb.Trailer{
Metadata: &pb.Metadata{},
StatusCode: uint32(codes.OK),
StatusMessage: "",
StatusDetails: nil,
},
},
PayloadTruncated: false,
Peer: nil,
},
},
{
config: &Cancel{
OnClientSide: true,
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CANCEL,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: nil,
PayloadTruncated: false,
Peer: nil,
},
},
// gRPC headers should be omitted.
{
config: &ClientHeader{
OnClientSide: false,
Header: map[string][]string{
"grpc-reserved": {"to be omitted"},
":authority": {"to be omitted"},
"a": {"b", "bb"},
},
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
Logger: pb.GrpcLogEntry_LOGGER_SERVER,
Payload: &pb.GrpcLogEntry_ClientHeader{
ClientHeader: &pb.ClientHeader{
Metadata: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "a", Value: []byte{'b'}},
{Key: "a", Value: []byte{'b', 'b'}},
},
},
},
},
PayloadTruncated: false,
},
},
{
config: &ServerHeader{
OnClientSide: true,
Header: map[string][]string{
"grpc-reserved": {"to be omitted"},
":authority": {"to be omitted"},
"a": {"b", "bb"},
},
},
want: &pb.GrpcLogEntry{
Timestamp: nil,
CallId: 1,
SequenceIdWithinCall: 0,
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
Logger: pb.GrpcLogEntry_LOGGER_CLIENT,
Payload: &pb.GrpcLogEntry_ServerHeader{
ServerHeader: &pb.ServerHeader{
Metadata: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "a", Value: []byte{'b'}},
{Key: "a", Value: []byte{'b', 'b'}},
},
},
},
},
PayloadTruncated: false,
},
},
}
for i, tc := range testCases {
buf.Reset()
tc.want.SequenceIdWithinCall = uint64(i + 1)
ml.Log(tc.config)
inSink := new(pb.GrpcLogEntry)
if err := proto.Unmarshal(buf.Bytes(), inSink); err != nil {
t.Errorf("failed to unmarshal bytes in sink to proto: %v", err)
continue
}
inSink.Timestamp = nil // Strip timestamp before comparing.
if !proto.Equal(inSink, tc.want) {
t.Errorf("Log(%+v), in sink: %+v, want %+v", tc.config, inSink, tc.want)
}
}
}
func TestTruncateMetadataNotTruncated(t *testing.T) {
testCases := []struct {
ml *MethodLogger
mpPb *pb.Metadata
}{
{
ml: newMethodLogger(maxUInt, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
},
},
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
},
},
},
{
ml: newMethodLogger(1, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: nil},
},
},
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1, 1}},
},
},
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
{Key: "", Value: []byte{1}},
},
},
},
// "grpc-trace-bin" is kept in log but not counted towards the size
// limit.
{
ml: newMethodLogger(1, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
{Key: "grpc-trace-bin", Value: []byte("some.trace.key")},
},
},
},
}
for i, tc := range testCases {
truncated := tc.ml.truncateMetadata(tc.mpPb)
if truncated {
t.Errorf("test case %v, returned truncated, want not truncated", i)
}
}
}
func TestTruncateMetadataTruncated(t *testing.T) {
testCases := []struct {
ml *MethodLogger
mpPb *pb.Metadata
entryLen int
}{
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1, 1, 1}},
},
},
entryLen: 0,
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
{Key: "", Value: []byte{1}},
{Key: "", Value: []byte{1}},
},
},
entryLen: 2,
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1, 1}},
{Key: "", Value: []byte{1}},
},
},
entryLen: 1,
},
{
ml: newMethodLogger(2, maxUInt),
mpPb: &pb.Metadata{
Entry: []*pb.MetadataEntry{
{Key: "", Value: []byte{1}},
{Key: "", Value: []byte{1, 1}},
},
},
entryLen: 1,
},
}
for i, tc := range testCases {
truncated := tc.ml.truncateMetadata(tc.mpPb)
if !truncated {
t.Errorf("test case %v, returned not truncated, want truncated", i)
continue
}
if len(tc.mpPb.Entry) != tc.entryLen {
t.Errorf("test case %v, entry length: %v, want: %v", i, len(tc.mpPb.Entry), tc.entryLen)
}
}
}
func TestTruncateMessageNotTruncated(t *testing.T) {
testCases := []struct {
ml *MethodLogger
msgPb *pb.Message
}{
{
ml: newMethodLogger(maxUInt, maxUInt),
msgPb: &pb.Message{
Data: []byte{1},
},
},
{
ml: newMethodLogger(maxUInt, 3),
msgPb: &pb.Message{
Data: []byte{1, 1},
},
},
{
ml: newMethodLogger(maxUInt, 2),
msgPb: &pb.Message{
Data: []byte{1, 1},
},
},
}
for i, tc := range testCases {
truncated := tc.ml.truncateMessage(tc.msgPb)
if truncated {
t.Errorf("test case %v, returned truncated, want not truncated", i)
}
}
}
func TestTruncateMessageTruncated(t *testing.T) {
testCases := []struct {
ml *MethodLogger
msgPb *pb.Message
oldLength uint32
}{
{
ml: newMethodLogger(maxUInt, 2),
msgPb: &pb.Message{
Length: 3,
Data: []byte{1, 1, 1},
},
oldLength: 3,
},
}
for i, tc := range testCases {
truncated := tc.ml.truncateMessage(tc.msgPb)
if !truncated {
t.Errorf("test case %v, returned not truncated, want truncated", i)
continue
}
if len(tc.msgPb.Data) != int(tc.ml.messageMaxLen) {
t.Errorf("test case %v, message length: %v, want: %v", i, len(tc.msgPb.Data), tc.ml.messageMaxLen)
}
if tc.msgPb.Length != tc.oldLength {
t.Errorf("test case %v, message.Length field: %v, want: %v", i, tc.msgPb.Length, tc.oldLength)
}
}
}

View File

@@ -1,33 +0,0 @@
#!/bin/bash
# Copyright 2018 gRPC 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.
set -eux -o pipefail
TMP=$(mktemp -d)
function finish {
rm -rf "$TMP"
}
trap finish EXIT
pushd "$TMP"
mkdir -p grpc/binarylog/grpc_binarylog_v1
curl https://raw.githubusercontent.com/grpc/grpc-proto/master/grpc/binlog/v1/binarylog.proto > grpc/binarylog/grpc_binarylog_v1/binarylog.proto
protoc --go_out=plugins=grpc,paths=source_relative:. -I. grpc/binarylog/grpc_binarylog_v1/*.proto
popd
rm -f ./grpc_binarylog_v1/*.pb.go
cp "$TMP"/grpc/binarylog/grpc_binarylog_v1/*.pb.go ../../binarylog/grpc_binarylog_v1/

View File

@@ -1,179 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"reflect"
"testing"
)
func TestLongMethodConfigRegexp(t *testing.T) {
testCases := []struct {
in string
out []string
}{
{in: "", out: nil},
{in: "*/m", out: nil},
{
in: "p.s/m{}",
out: []string{"p.s/m{}", "p.s", "m", "{}"},
},
{
in: "p.s/m",
out: []string{"p.s/m", "p.s", "m", ""},
},
{
in: "p.s/m{h}",
out: []string{"p.s/m{h}", "p.s", "m", "{h}"},
},
{
in: "p.s/m{m}",
out: []string{"p.s/m{m}", "p.s", "m", "{m}"},
},
{
in: "p.s/m{h:123}",
out: []string{"p.s/m{h:123}", "p.s", "m", "{h:123}"},
},
{
in: "p.s/m{m:123}",
out: []string{"p.s/m{m:123}", "p.s", "m", "{m:123}"},
},
{
in: "p.s/m{h:123,m:123}",
out: []string{"p.s/m{h:123,m:123}", "p.s", "m", "{h:123,m:123}"},
},
{
in: "p.s/*",
out: []string{"p.s/*", "p.s", "*", ""},
},
{
in: "p.s/*{h}",
out: []string{"p.s/*{h}", "p.s", "*", "{h}"},
},
{
in: "s/m*",
out: []string{"s/m*", "s", "m", "*"},
},
{
in: "s/**",
out: []string{"s/**", "s", "*", "*"},
},
}
for _, tc := range testCases {
match := longMethodConfigRegexp.FindStringSubmatch(tc.in)
if !reflect.DeepEqual(match, tc.out) {
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
}
}
}
func TestHeaderConfigRegexp(t *testing.T) {
testCases := []struct {
in string
out []string
}{
{in: "{}", out: nil},
{in: "{a:b}", out: nil},
{in: "{m:123}", out: nil},
{in: "{h:123;m:123}", out: nil},
{
in: "{h}",
out: []string{"{h}", ""},
},
{
in: "{h:123}",
out: []string{"{h:123}", "123"},
},
}
for _, tc := range testCases {
match := headerConfigRegexp.FindStringSubmatch(tc.in)
if !reflect.DeepEqual(match, tc.out) {
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
}
}
}
func TestMessageConfigRegexp(t *testing.T) {
testCases := []struct {
in string
out []string
}{
{in: "{}", out: nil},
{in: "{a:b}", out: nil},
{in: "{h:123}", out: nil},
{in: "{h:123;m:123}", out: nil},
{
in: "{m}",
out: []string{"{m}", ""},
},
{
in: "{m:123}",
out: []string{"{m:123}", "123"},
},
}
for _, tc := range testCases {
match := messageConfigRegexp.FindStringSubmatch(tc.in)
if !reflect.DeepEqual(match, tc.out) {
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
}
}
}
func TestHeaderMessageConfigRegexp(t *testing.T) {
testCases := []struct {
in string
out []string
}{
{in: "{}", out: nil},
{in: "{a:b}", out: nil},
{in: "{h}", out: nil},
{in: "{h:123}", out: nil},
{in: "{m}", out: nil},
{in: "{m:123}", out: nil},
{
in: "{h;m}",
out: []string{"{h;m}", "", ""},
},
{
in: "{h:123;m}",
out: []string{"{h:123;m}", "123", ""},
},
{
in: "{h;m:123}",
out: []string{"{h;m:123}", "", "123"},
},
{
in: "{h:123;m:123}",
out: []string{"{h:123;m:123}", "123", "123"},
},
}
for _, tc := range testCases {
match := headerMessageConfigRegexp.FindStringSubmatch(tc.in)
if !reflect.DeepEqual(match, tc.out) {
t.Errorf("in: %q, out: %q, want: %q", tc.in, match, tc.out)
}
}
}

View File

@@ -1,64 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"io"
"github.com/golang/protobuf/proto"
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
"google.golang.org/grpc/grpclog"
)
var (
defaultSink Sink = &noopSink{} // TODO(blog): change this default (file in /tmp).
)
// SetDefaultSink sets the sink where binary logs will be written to.
//
// Not thread safe. Only set during initialization.
func SetDefaultSink(s Sink) {
defaultSink = s
}
// Sink writes log entry into the binary log sink.
type Sink interface {
Write(*pb.GrpcLogEntry)
}
type noopSink struct{}
func (ns *noopSink) Write(*pb.GrpcLogEntry) {}
// NewWriterSink creates a binary log sink with the given writer.
func NewWriterSink(w io.Writer) Sink {
return &writerSink{out: w}
}
type writerSink struct {
out io.Writer
}
func (fs *writerSink) Write(e *pb.GrpcLogEntry) {
b, err := proto.Marshal(e)
if err != nil {
grpclog.Infof("binary logging: failed to marshal proto message: %v", err)
}
fs.out.Write(b)
}

View File

@@ -1,41 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import (
"errors"
"strings"
)
// parseMethodName splits service and method from the input. It expects format
// "/service/method".
//
// TODO: move to internal/grpcutil.
func parseMethodName(methodName string) (service, method string, _ error) {
if !strings.HasPrefix(methodName, "/") {
return "", "", errors.New("invalid method name: should start with /")
}
methodName = methodName[1:]
pos := strings.LastIndex(methodName, "/")
if pos < 0 {
return "", "", errors.New("invalid method name: suffix /method is missing")
}
return methodName[:pos], methodName[pos+1:], nil
}

View File

@@ -1,59 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package binarylog
import "testing"
func TestParseMethodName(t *testing.T) {
testCases := []struct {
methodName string
service, method string
}{
{methodName: "/s/m", service: "s", method: "m"},
{methodName: "/p.s/m", service: "p.s", method: "m"},
{methodName: "/p/s/m", service: "p/s", method: "m"},
}
for _, tc := range testCases {
s, m, err := parseMethodName(tc.methodName)
if err != nil {
t.Errorf("Parsing %q got error %v, want nil", tc.methodName, err)
continue
}
if s != tc.service || m != tc.method {
t.Errorf("Parseing %q got service %q, method %q, want service %q, method %q",
tc.methodName, s, m, tc.service, tc.method,
)
}
}
}
func TestParseMethodNameInvalid(t *testing.T) {
testCases := []string{
"/",
"/sm",
"",
"sm",
}
for _, tc := range testCases {
_, _, err := parseMethodName(tc)
if err == nil {
t.Errorf("Parsing %q got nil error, want non-nil error", tc)
}
}
}

View File

@@ -1,90 +0,0 @@
// +build linux,go1.10,!appengine
/*
*
* Copyright 2018 gRPC 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.
*
*/
// The test in this file should be run in an environment that has go1.10 or later,
// as the function SyscallConn() (required to get socket option) was introduced
// to net.TCPListener in go1.10.
package channelz_test
import (
"net"
"reflect"
"syscall"
"testing"
"golang.org/x/sys/unix"
"google.golang.org/grpc/internal/channelz"
)
func TestGetSocketOpt(t *testing.T) {
network, addr := "tcp", ":0"
ln, err := net.Listen(network, addr)
if err != nil {
t.Fatalf("net.Listen(%s,%s) failed with err: %v", network, addr, err)
}
defer ln.Close()
go func() {
ln.Accept()
}()
conn, _ := net.Dial(network, ln.Addr().String())
defer conn.Close()
tcpc := conn.(*net.TCPConn)
raw, err := tcpc.SyscallConn()
if err != nil {
t.Fatalf("SyscallConn() failed due to %v", err)
}
l := &unix.Linger{Onoff: 1, Linger: 5}
recvTimout := &unix.Timeval{Sec: 100}
sendTimeout := &unix.Timeval{Sec: 8888}
raw.Control(func(fd uintptr) {
err := unix.SetsockoptLinger(int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, l)
if err != nil {
t.Fatalf("failed to SetsockoptLinger(%v,%v,%v,%v) due to %v", int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, l, err)
}
err = unix.SetsockoptTimeval(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, recvTimout)
if err != nil {
t.Fatalf("failed to SetsockoptTimeval(%v,%v,%v,%v) due to %v", int(fd), syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, recvTimout, err)
}
err = unix.SetsockoptTimeval(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, sendTimeout)
if err != nil {
t.Fatalf("failed to SetsockoptTimeval(%v,%v,%v,%v) due to %v", int(fd), syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, sendTimeout, err)
}
})
sktopt := channelz.GetSocketOption(conn)
if !reflect.DeepEqual(sktopt.Linger, l) {
t.Fatalf("get socket option linger, want: %v, got %v", l, sktopt.Linger)
}
if !reflect.DeepEqual(sktopt.RecvTimeout, recvTimout) {
t.Logf("get socket option recv timeout, want: %v, got %v, may be caused by system allowing non or partial setting of this value", recvTimout, sktopt.RecvTimeout)
}
if !reflect.DeepEqual(sktopt.SendTimeout, sendTimeout) {
t.Logf("get socket option send timeout, want: %v, got %v, may be caused by system allowing non or partial setting of this value", sendTimeout, sktopt.SendTimeout)
}
if sktopt == nil || sktopt.TCPInfo != nil && sktopt.TCPInfo.State != 1 {
t.Fatalf("TCPInfo.State want 1 (TCP_ESTABLISHED), got %v", sktopt)
}
sktopt = channelz.GetSocketOption(ln)
if sktopt == nil || sktopt.TCPInfo == nil || sktopt.TCPInfo.State != 10 {
t.Fatalf("TCPInfo.State want 10 (TCP_LISTEN), got %v", sktopt)
}
}

View File

@@ -1,61 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
// Package grpcsync implements additional synchronization primitives built upon
// the sync package.
package grpcsync
import "sync"
// Event represents a one-time event that may occur in the future.
type Event struct {
c chan struct{}
o sync.Once
}
// Fire causes e to complete. It is safe to call multiple times, and
// concurrently. It returns true iff this call to Fire caused the signaling
// channel returned by Done to close.
func (e *Event) Fire() bool {
ret := false
e.o.Do(func() {
close(e.c)
ret = true
})
return ret
}
// Done returns a channel that will be closed when Fire is called.
func (e *Event) Done() <-chan struct{} {
return e.c
}
// HasFired returns true if Fire has been called.
func (e *Event) HasFired() bool {
select {
case <-e.c:
return true
default:
return false
}
}
// NewEvent returns a new, ready-to-use Event.
func NewEvent() *Event {
return &Event{c: make(chan struct{})}
}

View File

@@ -1,69 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package grpcsync
import "testing"
func TestEventHasFired(t *testing.T) {
e := NewEvent()
if e.HasFired() {
t.Fatal("e.HasFired() = true; want false")
}
if !e.Fire() {
t.Fatal("e.Fire() = false; want true")
}
if !e.HasFired() {
t.Fatal("e.HasFired() = false; want true")
}
}
func TestEventDoneChannel(t *testing.T) {
e := NewEvent()
select {
case <-e.Done():
t.Fatal("e.HasFired() = true; want false")
default:
}
if !e.Fire() {
t.Fatal("e.Fire() = false; want true")
}
select {
case <-e.Done():
default:
t.Fatal("e.HasFired() = false; want true")
}
}
func TestEventMultipleFires(t *testing.T) {
e := NewEvent()
if e.HasFired() {
t.Fatal("e.HasFired() = true; want false")
}
if !e.Fire() {
t.Fatal("e.Fire() = false; want true")
}
for i := 0; i < 3; i++ {
if !e.HasFired() {
t.Fatal("e.HasFired() = false; want true")
}
if e.Fire() {
t.Fatal("e.Fire() = true; want false")
}
}
}

View File

@@ -1,118 +0,0 @@
/*
*
* Copyright 2017 gRPC 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.
*
*/
// Package leakcheck contains functions to check leaked goroutines.
//
// Call "defer leakcheck.Check(t)" at the beginning of tests.
package leakcheck
import (
"runtime"
"sort"
"strings"
"time"
)
var goroutinesToIgnore = []string{
"testing.Main(",
"testing.tRunner(",
"testing.(*M).",
"runtime.goexit",
"created by runtime.gc",
"created by runtime/trace.Start",
"interestingGoroutines",
"runtime.MHeap_Scavenger",
"signal.signal_recv",
"sigterm.handler",
"runtime_mcall",
"(*loggingT).flushDaemon",
"goroutine in C code",
}
// RegisterIgnoreGoroutine appends s into the ignore goroutine list. The
// goroutines whose stack trace contains s will not be identified as leaked
// goroutines. Not thread-safe, only call this function in init().
func RegisterIgnoreGoroutine(s string) {
goroutinesToIgnore = append(goroutinesToIgnore, s)
}
func ignore(g string) bool {
sl := strings.SplitN(g, "\n", 2)
if len(sl) != 2 {
return true
}
stack := strings.TrimSpace(sl[1])
if strings.HasPrefix(stack, "testing.RunTests") {
return true
}
if stack == "" {
return true
}
for _, s := range goroutinesToIgnore {
if strings.Contains(stack, s) {
return true
}
}
return false
}
// interestingGoroutines returns all goroutines we care about for the purpose of
// leak checking. It excludes testing or runtime ones.
func interestingGoroutines() (gs []string) {
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
for _, g := range strings.Split(string(buf), "\n\n") {
if !ignore(g) {
gs = append(gs, g)
}
}
sort.Strings(gs)
return
}
// Errorfer is the interface that wraps the Errorf method. It's a subset of
// testing.TB to make it easy to use Check.
type Errorfer interface {
Errorf(format string, args ...interface{})
}
func check(efer Errorfer, timeout time.Duration) {
// Loop, waiting for goroutines to shut down.
// Wait up to timeout, but finish as quickly as possible.
deadline := time.Now().Add(timeout)
var leaked []string
for time.Now().Before(deadline) {
if leaked = interestingGoroutines(); len(leaked) == 0 {
return
}
time.Sleep(50 * time.Millisecond)
}
for _, g := range leaked {
efer.Errorf("Leaked goroutine: %v", g)
}
}
// Check looks at the currently-running goroutines and checks if there are any
// interestring (created by gRPC) goroutines leaked. It waits up to 10 seconds
// in the error cases.
func Check(efer Errorfer) {
check(efer, 10*time.Second)
}

View File

@@ -1,76 +0,0 @@
/*
*
* Copyright 2017 gRPC 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.
*
*/
package leakcheck
import (
"fmt"
"strings"
"testing"
"time"
)
type testErrorfer struct {
errorCount int
errors []string
}
func (e *testErrorfer) Errorf(format string, args ...interface{}) {
e.errors = append(e.errors, fmt.Sprintf(format, args...))
e.errorCount++
}
func TestCheck(t *testing.T) {
const leakCount = 3
for i := 0; i < leakCount; i++ {
go func() { time.Sleep(2 * time.Second) }()
}
if ig := interestingGoroutines(); len(ig) == 0 {
t.Error("blah")
}
e := &testErrorfer{}
check(e, time.Second)
if e.errorCount != leakCount {
t.Errorf("check found %v leaks, want %v leaks", e.errorCount, leakCount)
t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n"))
}
check(t, 3*time.Second)
}
func ignoredTestingLeak(d time.Duration) {
time.Sleep(d)
}
func TestCheckRegisterIgnore(t *testing.T) {
RegisterIgnoreGoroutine("ignoredTestingLeak")
const leakCount = 3
for i := 0; i < leakCount; i++ {
go func() { time.Sleep(2 * time.Second) }()
}
go func() { ignoredTestingLeak(3 * time.Second) }()
if ig := interestingGoroutines(); len(ig) == 0 {
t.Error("blah")
}
e := &testErrorfer{}
check(e, time.Second)
if e.errorCount != leakCount {
t.Errorf("check found %v leaks, want %v leaks", e.errorCount, leakCount)
t.Logf("leaked goroutines:\n%v", strings.Join(e.errors, "\n"))
}
check(t, 3*time.Second)
}

View File

@@ -1,67 +0,0 @@
// +build !appengine,go1.7
/*
*
* Copyright 2018 gRPC 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.
*
*/
// Package syscall provides functionalities that grpc uses to get low-level operating system
// stats/info.
package syscall
import (
"syscall"
"golang.org/x/sys/unix"
"google.golang.org/grpc/grpclog"
)
// GetCPUTime returns the how much CPU time has passed since the start of this process.
func GetCPUTime() int64 {
var ts unix.Timespec
if err := unix.ClockGettime(unix.CLOCK_PROCESS_CPUTIME_ID, &ts); err != nil {
grpclog.Fatal(err)
}
return ts.Nano()
}
// Rusage is an alias for syscall.Rusage under linux non-appengine environment.
type Rusage syscall.Rusage
// GetRusage returns the resource usage of current process.
func GetRusage() (rusage *Rusage) {
rusage = new(Rusage)
syscall.Getrusage(syscall.RUSAGE_SELF, (*syscall.Rusage)(rusage))
return
}
// CPUTimeDiff returns the differences of user CPU time and system CPU time used
// between two Rusage structs.
func CPUTimeDiff(first *Rusage, latest *Rusage) (float64, float64) {
f := (*syscall.Rusage)(first)
l := (*syscall.Rusage)(latest)
var (
utimeDiffs = l.Utime.Sec - f.Utime.Sec
utimeDiffus = l.Utime.Usec - f.Utime.Usec
stimeDiffs = l.Stime.Sec - f.Stime.Sec
stimeDiffus = l.Stime.Usec - f.Stime.Usec
)
uTimeElapsed := float64(utimeDiffs) + float64(utimeDiffus)*1.0e-6
sTimeElapsed := float64(stimeDiffs) + float64(stimeDiffus)*1.0e-6
return uTimeElapsed, sTimeElapsed
}

View File

@@ -1,47 +0,0 @@
// +build !linux appengine !go1.7
/*
*
* Copyright 2018 gRPC 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.
*
*/
package syscall
import "google.golang.org/grpc/grpclog"
func init() {
grpclog.Info("CPU time info is unavailable on non-linux or appengine environment.")
}
// GetCPUTime returns the how much CPU time has passed since the start of this process.
// It always returns 0 under non-linux or appengine environment.
func GetCPUTime() int64 {
return 0
}
// Rusage is an empty struct under non-linux or appengine environment.
type Rusage struct{}
// GetRusage is a no-op function under non-linux or appengine environment.
func GetRusage() (rusage *Rusage) {
return nil
}
// CPUTimeDiff returns the differences of user CPU time and system CPU time used
// between two Rusage structs. It a no-op function for non-linux or appengine environment.
func CPUTimeDiff(first *Rusage, latest *Rusage) (float64, float64) {
return 0, 0
}

View File

@@ -1,95 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package testutils
import (
"errors"
"net"
"time"
)
var errClosed = errors.New("closed")
type pipeAddr struct{}
func (p pipeAddr) Network() string { return "pipe" }
func (p pipeAddr) String() string { return "pipe" }
// PipeListener is a listener with an unbuffered pipe. Each write will complete only once the other side reads. It
// should only be created using NewPipeListener.
type PipeListener struct {
c chan chan<- net.Conn
done chan struct{}
}
// NewPipeListener creates a new pipe listener.
func NewPipeListener() *PipeListener {
return &PipeListener{
c: make(chan chan<- net.Conn),
done: make(chan struct{}),
}
}
// Accept accepts a connection.
func (p *PipeListener) Accept() (net.Conn, error) {
var connChan chan<- net.Conn
select {
case <-p.done:
return nil, errClosed
case connChan = <-p.c:
select {
case <-p.done:
close(connChan)
return nil, errClosed
default:
}
}
c1, c2 := net.Pipe()
connChan <- c1
close(connChan)
return c2, nil
}
// Close closes the listener.
func (p *PipeListener) Close() error {
close(p.done)
return nil
}
// Addr returns a pipe addr.
func (p *PipeListener) Addr() net.Addr {
return pipeAddr{}
}
// Dialer dials a connection.
func (p *PipeListener) Dialer() func(string, time.Duration) (net.Conn, error) {
return func(string, time.Duration) (net.Conn, error) {
connChan := make(chan net.Conn)
select {
case p.c <- connChan:
case <-p.done:
return nil, errClosed
}
conn, ok := <-connChan
if !ok {
return nil, errClosed
}
return conn, nil
}
}

View File

@@ -1,163 +0,0 @@
/*
*
* Copyright 2018 gRPC 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.
*
*/
package testutils_test
import (
"testing"
"time"
"google.golang.org/grpc/internal/testutils"
)
func TestPipeListener(t *testing.T) {
pl := testutils.NewPipeListener()
recvdBytes := make(chan []byte)
const want = "hello world"
go func() {
c, err := pl.Accept()
if err != nil {
t.Error(err)
}
read := make([]byte, len(want))
_, err = c.Read(read)
if err != nil {
t.Error(err)
}
recvdBytes <- read
}()
dl := pl.Dialer()
conn, err := dl("", time.Duration(0))
if err != nil {
t.Fatal(err)
}
_, err = conn.Write([]byte(want))
if err != nil {
t.Fatal(err)
}
select {
case gotBytes := <-recvdBytes:
got := string(gotBytes)
if got != want {
t.Fatalf("expected to get %s, got %s", got, want)
}
case <-time.After(100 * time.Millisecond):
t.Fatal("timed out waiting for server to receive bytes")
}
}
func TestUnblocking(t *testing.T) {
for _, test := range []struct {
desc string
blockFuncShouldError bool
blockFunc func(*testutils.PipeListener, chan struct{}) error
unblockFunc func(*testutils.PipeListener) error
}{
{
desc: "Accept unblocks Dial",
blockFunc: func(pl *testutils.PipeListener, done chan struct{}) error {
dl := pl.Dialer()
_, err := dl("", time.Duration(0))
close(done)
return err
},
unblockFunc: func(pl *testutils.PipeListener) error {
_, err := pl.Accept()
return err
},
},
{
desc: "Close unblocks Dial",
blockFuncShouldError: true, // because pl.Close will be called
blockFunc: func(pl *testutils.PipeListener, done chan struct{}) error {
dl := pl.Dialer()
_, err := dl("", time.Duration(0))
close(done)
return err
},
unblockFunc: func(pl *testutils.PipeListener) error {
return pl.Close()
},
},
{
desc: "Dial unblocks Accept",
blockFunc: func(pl *testutils.PipeListener, done chan struct{}) error {
_, err := pl.Accept()
close(done)
return err
},
unblockFunc: func(pl *testutils.PipeListener) error {
dl := pl.Dialer()
_, err := dl("", time.Duration(0))
return err
},
},
{
desc: "Close unblocks Accept",
blockFuncShouldError: true, // because pl.Close will be called
blockFunc: func(pl *testutils.PipeListener, done chan struct{}) error {
_, err := pl.Accept()
close(done)
return err
},
unblockFunc: func(pl *testutils.PipeListener) error {
return pl.Close()
},
},
} {
t.Log(test.desc)
testUnblocking(t, test.blockFunc, test.unblockFunc, test.blockFuncShouldError)
}
}
func testUnblocking(t *testing.T, blockFunc func(*testutils.PipeListener, chan struct{}) error, unblockFunc func(*testutils.PipeListener) error, blockFuncShouldError bool) {
pl := testutils.NewPipeListener()
dialFinished := make(chan struct{})
go func() {
err := blockFunc(pl, dialFinished)
if blockFuncShouldError && err == nil {
t.Error("expected blocking func to return error because pl.Close was called, but got nil")
}
if !blockFuncShouldError && err != nil {
t.Error(err)
}
}()
select {
case <-dialFinished:
t.Fatal("expected Dial to block until pl.Close or pl.Accept")
default:
}
if err := unblockFunc(pl); err != nil {
t.Fatal(err)
}
select {
case <-dialFinished:
case <-time.After(100 * time.Millisecond):
t.Fatal("expected Accept to unblock after pl.Accept was called")
}
}

View File

@@ -1,481 +0,0 @@
/*
*
* Copyright 2016 gRPC 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.
*
*/
package transport
import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"sync"
"testing"
"time"
"github.com/golang/protobuf/proto"
dpb "github.com/golang/protobuf/ptypes/duration"
"golang.org/x/net/context"
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func TestHandlerTransport_NewServerHandlerTransport(t *testing.T) {
type testCase struct {
name string
req *http.Request
wantErr string
modrw func(http.ResponseWriter) http.ResponseWriter
check func(*serverHandlerTransport, *testCase) error
}
tests := []testCase{
{
name: "http/1.1",
req: &http.Request{
ProtoMajor: 1,
ProtoMinor: 1,
},
wantErr: "gRPC requires HTTP/2",
},
{
name: "bad method",
req: &http.Request{
ProtoMajor: 2,
Method: "GET",
Header: http.Header{},
RequestURI: "/",
},
wantErr: "invalid gRPC request method",
},
{
name: "bad content type",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/foo"},
},
RequestURI: "/service/foo.bar",
},
wantErr: "invalid gRPC request content-type",
},
{
name: "not flusher",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/grpc"},
},
RequestURI: "/service/foo.bar",
},
modrw: func(w http.ResponseWriter) http.ResponseWriter {
// Return w without its Flush method
type onlyCloseNotifier interface {
http.ResponseWriter
http.CloseNotifier
}
return struct{ onlyCloseNotifier }{w.(onlyCloseNotifier)}
},
wantErr: "gRPC requires a ResponseWriter supporting http.Flusher",
},
{
name: "not closenotifier",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/grpc"},
},
RequestURI: "/service/foo.bar",
},
modrw: func(w http.ResponseWriter) http.ResponseWriter {
// Return w without its CloseNotify method
type onlyFlusher interface {
http.ResponseWriter
http.Flusher
}
return struct{ onlyFlusher }{w.(onlyFlusher)}
},
wantErr: "gRPC requires a ResponseWriter supporting http.CloseNotifier",
},
{
name: "valid",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/grpc"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
},
check: func(t *serverHandlerTransport, tt *testCase) error {
if t.req != tt.req {
return fmt.Errorf("t.req = %p; want %p", t.req, tt.req)
}
if t.rw == nil {
return errors.New("t.rw = nil; want non-nil")
}
return nil
},
},
{
name: "with timeout",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": []string{"application/grpc"},
"Grpc-Timeout": {"200m"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
},
check: func(t *serverHandlerTransport, tt *testCase) error {
if !t.timeoutSet {
return errors.New("timeout not set")
}
if want := 200 * time.Millisecond; t.timeout != want {
return fmt.Errorf("timeout = %v; want %v", t.timeout, want)
}
return nil
},
},
{
name: "with bad timeout",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": []string{"application/grpc"},
"Grpc-Timeout": {"tomorrow"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
},
wantErr: `rpc error: code = Internal desc = malformed time-out: transport: timeout unit is not recognized: "tomorrow"`,
},
{
name: "with metadata",
req: &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": []string{"application/grpc"},
"meta-foo": {"foo-val"},
"meta-bar": {"bar-val1", "bar-val2"},
"user-agent": {"x/y a/b"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
},
check: func(ht *serverHandlerTransport, tt *testCase) error {
want := metadata.MD{
"meta-bar": {"bar-val1", "bar-val2"},
"user-agent": {"x/y a/b"},
"meta-foo": {"foo-val"},
"content-type": {"application/grpc"},
}
if !reflect.DeepEqual(ht.headerMD, want) {
return fmt.Errorf("metdata = %#v; want %#v", ht.headerMD, want)
}
return nil
},
},
}
for _, tt := range tests {
rw := newTestHandlerResponseWriter()
if tt.modrw != nil {
rw = tt.modrw(rw)
}
got, gotErr := NewServerHandlerTransport(rw, tt.req, nil)
if (gotErr != nil) != (tt.wantErr != "") || (gotErr != nil && gotErr.Error() != tt.wantErr) {
t.Errorf("%s: error = %q; want %q", tt.name, gotErr.Error(), tt.wantErr)
continue
}
if gotErr != nil {
continue
}
if tt.check != nil {
if err := tt.check(got.(*serverHandlerTransport), &tt); err != nil {
t.Errorf("%s: %v", tt.name, err)
}
}
}
}
type testHandlerResponseWriter struct {
*httptest.ResponseRecorder
closeNotify chan bool
}
func (w testHandlerResponseWriter) CloseNotify() <-chan bool { return w.closeNotify }
func (w testHandlerResponseWriter) Flush() {}
func newTestHandlerResponseWriter() http.ResponseWriter {
return testHandlerResponseWriter{
ResponseRecorder: httptest.NewRecorder(),
closeNotify: make(chan bool, 1),
}
}
type handleStreamTest struct {
t *testing.T
bodyw *io.PipeWriter
rw testHandlerResponseWriter
ht *serverHandlerTransport
}
func newHandleStreamTest(t *testing.T) *handleStreamTest {
bodyr, bodyw := io.Pipe()
req := &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/grpc"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
Body: bodyr,
}
rw := newTestHandlerResponseWriter().(testHandlerResponseWriter)
ht, err := NewServerHandlerTransport(rw, req, nil)
if err != nil {
t.Fatal(err)
}
return &handleStreamTest{
t: t,
bodyw: bodyw,
ht: ht.(*serverHandlerTransport),
rw: rw,
}
}
func TestHandlerTransport_HandleStreams(t *testing.T) {
st := newHandleStreamTest(t)
handleStream := func(s *Stream) {
if want := "/service/foo.bar"; s.method != want {
t.Errorf("stream method = %q; want %q", s.method, want)
}
st.bodyw.Close() // no body
st.ht.WriteStatus(s, status.New(codes.OK, ""))
}
st.ht.HandleStreams(
func(s *Stream) { go handleStream(s) },
func(ctx context.Context, method string) context.Context { return ctx },
)
wantHeader := http.Header{
"Date": nil,
"Content-Type": {"application/grpc"},
"Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"},
"Grpc-Status": {"0"},
}
if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) {
t.Errorf("Header+Trailer Map: %#v; want %#v", st.rw.HeaderMap, wantHeader)
}
}
// Tests that codes.Unimplemented will close the body, per comment in handler_server.go.
func TestHandlerTransport_HandleStreams_Unimplemented(t *testing.T) {
handleStreamCloseBodyTest(t, codes.Unimplemented, "thingy is unimplemented")
}
// Tests that codes.InvalidArgument will close the body, per comment in handler_server.go.
func TestHandlerTransport_HandleStreams_InvalidArgument(t *testing.T) {
handleStreamCloseBodyTest(t, codes.InvalidArgument, "bad arg")
}
func handleStreamCloseBodyTest(t *testing.T, statusCode codes.Code, msg string) {
st := newHandleStreamTest(t)
handleStream := func(s *Stream) {
st.ht.WriteStatus(s, status.New(statusCode, msg))
}
st.ht.HandleStreams(
func(s *Stream) { go handleStream(s) },
func(ctx context.Context, method string) context.Context { return ctx },
)
wantHeader := http.Header{
"Date": nil,
"Content-Type": {"application/grpc"},
"Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"},
"Grpc-Status": {fmt.Sprint(uint32(statusCode))},
"Grpc-Message": {encodeGrpcMessage(msg)},
}
if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) {
t.Errorf("Header+Trailer mismatch.\n got: %#v\nwant: %#v", st.rw.HeaderMap, wantHeader)
}
}
func TestHandlerTransport_HandleStreams_Timeout(t *testing.T) {
bodyr, bodyw := io.Pipe()
req := &http.Request{
ProtoMajor: 2,
Method: "POST",
Header: http.Header{
"Content-Type": {"application/grpc"},
"Grpc-Timeout": {"200m"},
},
URL: &url.URL{
Path: "/service/foo.bar",
},
RequestURI: "/service/foo.bar",
Body: bodyr,
}
rw := newTestHandlerResponseWriter().(testHandlerResponseWriter)
ht, err := NewServerHandlerTransport(rw, req, nil)
if err != nil {
t.Fatal(err)
}
runStream := func(s *Stream) {
defer bodyw.Close()
select {
case <-s.ctx.Done():
case <-time.After(5 * time.Second):
t.Errorf("timeout waiting for ctx.Done")
return
}
err := s.ctx.Err()
if err != context.DeadlineExceeded {
t.Errorf("ctx.Err = %v; want %v", err, context.DeadlineExceeded)
return
}
ht.WriteStatus(s, status.New(codes.DeadlineExceeded, "too slow"))
}
ht.HandleStreams(
func(s *Stream) { go runStream(s) },
func(ctx context.Context, method string) context.Context { return ctx },
)
wantHeader := http.Header{
"Date": nil,
"Content-Type": {"application/grpc"},
"Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"},
"Grpc-Status": {"4"},
"Grpc-Message": {encodeGrpcMessage("too slow")},
}
if !reflect.DeepEqual(rw.HeaderMap, wantHeader) {
t.Errorf("Header+Trailer Map mismatch.\n got: %#v\nwant: %#v", rw.HeaderMap, wantHeader)
}
}
// TestHandlerTransport_HandleStreams_MultiWriteStatus ensures that
// concurrent "WriteStatus"s do not panic writing to closed "writes" channel.
func TestHandlerTransport_HandleStreams_MultiWriteStatus(t *testing.T) {
testHandlerTransportHandleStreams(t, func(st *handleStreamTest, s *Stream) {
if want := "/service/foo.bar"; s.method != want {
t.Errorf("stream method = %q; want %q", s.method, want)
}
st.bodyw.Close() // no body
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
st.ht.WriteStatus(s, status.New(codes.OK, ""))
}()
}
wg.Wait()
})
}
// TestHandlerTransport_HandleStreams_WriteStatusWrite ensures that "Write"
// following "WriteStatus" does not panic writing to closed "writes" channel.
func TestHandlerTransport_HandleStreams_WriteStatusWrite(t *testing.T) {
testHandlerTransportHandleStreams(t, func(st *handleStreamTest, s *Stream) {
if want := "/service/foo.bar"; s.method != want {
t.Errorf("stream method = %q; want %q", s.method, want)
}
st.bodyw.Close() // no body
st.ht.WriteStatus(s, status.New(codes.OK, ""))
st.ht.Write(s, []byte("hdr"), []byte("data"), &Options{})
})
}
func testHandlerTransportHandleStreams(t *testing.T, handleStream func(st *handleStreamTest, s *Stream)) {
st := newHandleStreamTest(t)
st.ht.HandleStreams(
func(s *Stream) { go handleStream(st, s) },
func(ctx context.Context, method string) context.Context { return ctx },
)
}
func TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) {
errDetails := []proto.Message{
&epb.RetryInfo{
RetryDelay: &dpb.Duration{Seconds: 60},
},
&epb.ResourceInfo{
ResourceType: "foo bar",
ResourceName: "service.foo.bar",
Owner: "User",
},
}
statusCode := codes.ResourceExhausted
msg := "you are being throttled"
st, err := status.New(statusCode, msg).WithDetails(errDetails...)
if err != nil {
t.Fatal(err)
}
stBytes, err := proto.Marshal(st.Proto())
if err != nil {
t.Fatal(err)
}
hst := newHandleStreamTest(t)
handleStream := func(s *Stream) {
hst.ht.WriteStatus(s, st)
}
hst.ht.HandleStreams(
func(s *Stream) { go handleStream(s) },
func(ctx context.Context, method string) context.Context { return ctx },
)
wantHeader := http.Header{
"Date": nil,
"Content-Type": {"application/grpc"},
"Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"},
"Grpc-Status": {fmt.Sprint(uint32(statusCode))},
"Grpc-Message": {encodeGrpcMessage(msg)},
"Grpc-Status-Details-Bin": {encodeBinHeader(stBytes)},
}
if !reflect.DeepEqual(hst.rw.HeaderMap, wantHeader) {
t.Errorf("Header+Trailer mismatch.\n got: %#v\nwant: %#v", hst.rw.HeaderMap, wantHeader)
}
}

View File

@@ -1,237 +0,0 @@
/*
*
* Copyright 2014 gRPC 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.
*
*/
package transport
import (
"fmt"
"reflect"
"testing"
"time"
)
func TestTimeoutEncode(t *testing.T) {
for _, test := range []struct {
in string
out string
}{
{"12345678ns", "12345678n"},
{"123456789ns", "123457u"},
{"12345678us", "12345678u"},
{"123456789us", "123457m"},
{"12345678ms", "12345678m"},
{"123456789ms", "123457S"},
{"12345678s", "12345678S"},
{"123456789s", "2057614M"},
{"12345678m", "12345678M"},
{"123456789m", "2057614H"},
} {
d, err := time.ParseDuration(test.in)
if err != nil {
t.Fatalf("failed to parse duration string %s: %v", test.in, err)
}
out := encodeTimeout(d)
if out != test.out {
t.Fatalf("timeoutEncode(%s) = %s, want %s", test.in, out, test.out)
}
}
}
func TestTimeoutDecode(t *testing.T) {
for _, test := range []struct {
// input
s string
// output
d time.Duration
err error
}{
{"1234S", time.Second * 1234, nil},
{"1234x", 0, fmt.Errorf("transport: timeout unit is not recognized: %q", "1234x")},
{"1", 0, fmt.Errorf("transport: timeout string is too short: %q", "1")},
{"", 0, fmt.Errorf("transport: timeout string is too short: %q", "")},
} {
d, err := decodeTimeout(test.s)
if d != test.d || fmt.Sprint(err) != fmt.Sprint(test.err) {
t.Fatalf("timeoutDecode(%q) = %d, %v, want %d, %v", test.s, int64(d), err, int64(test.d), test.err)
}
}
}
func TestContentSubtype(t *testing.T) {
tests := []struct {
contentType string
want string
wantValid bool
}{
{"application/grpc", "", true},
{"application/grpc+", "", true},
{"application/grpc+blah", "blah", true},
{"application/grpc;", "", true},
{"application/grpc;blah", "blah", true},
{"application/grpcd", "", false},
{"application/grpd", "", false},
{"application/grp", "", false},
}
for _, tt := range tests {
got, gotValid := contentSubtype(tt.contentType)
if got != tt.want || gotValid != tt.wantValid {
t.Errorf("contentSubtype(%q) = (%v, %v); want (%v, %v)", tt.contentType, got, gotValid, tt.want, tt.wantValid)
}
}
}
func TestEncodeGrpcMessage(t *testing.T) {
for _, tt := range []struct {
input string
expected string
}{
{"", ""},
{"Hello", "Hello"},
{"\u0000", "%00"},
{"%", "%25"},
{"系统", "%E7%B3%BB%E7%BB%9F"},
{string([]byte{0xff, 0xfe, 0xfd}), "%EF%BF%BD%EF%BF%BD%EF%BF%BD"},
} {
actual := encodeGrpcMessage(tt.input)
if tt.expected != actual {
t.Errorf("encodeGrpcMessage(%q) = %q, want %q", tt.input, actual, tt.expected)
}
}
// make sure that all the visible ASCII chars except '%' are not percent encoded.
for i := ' '; i <= '~' && i != '%'; i++ {
output := encodeGrpcMessage(string(i))
if output != string(i) {
t.Errorf("encodeGrpcMessage(%v) = %v, want %v", string(i), output, string(i))
}
}
// make sure that all the invisible ASCII chars and '%' are percent encoded.
for i := rune(0); i == '%' || (i >= rune(0) && i < ' ') || (i > '~' && i <= rune(127)); i++ {
output := encodeGrpcMessage(string(i))
expected := fmt.Sprintf("%%%02X", i)
if output != expected {
t.Errorf("encodeGrpcMessage(%v) = %v, want %v", string(i), output, expected)
}
}
}
func TestDecodeGrpcMessage(t *testing.T) {
for _, tt := range []struct {
input string
expected string
}{
{"", ""},
{"Hello", "Hello"},
{"H%61o", "Hao"},
{"H%6", "H%6"},
{"%G0", "%G0"},
{"%E7%B3%BB%E7%BB%9F", "系统"},
{"%EF%BF%BD", "<22>"},
} {
actual := decodeGrpcMessage(tt.input)
if tt.expected != actual {
t.Errorf("decodeGrpcMessage(%q) = %q, want %q", tt.input, actual, tt.expected)
}
}
// make sure that all the visible ASCII chars except '%' are not percent decoded.
for i := ' '; i <= '~' && i != '%'; i++ {
output := decodeGrpcMessage(string(i))
if output != string(i) {
t.Errorf("decodeGrpcMessage(%v) = %v, want %v", string(i), output, string(i))
}
}
// make sure that all the invisible ASCII chars and '%' are percent decoded.
for i := rune(0); i == '%' || (i >= rune(0) && i < ' ') || (i > '~' && i <= rune(127)); i++ {
output := decodeGrpcMessage(fmt.Sprintf("%%%02X", i))
if output != string(i) {
t.Errorf("decodeGrpcMessage(%v) = %v, want %v", fmt.Sprintf("%%%02X", i), output, string(i))
}
}
}
// Decode an encoded string should get the same thing back, except for invalid
// utf8 chars.
func TestDecodeEncodeGrpcMessage(t *testing.T) {
testCases := []struct {
orig string
want string
}{
{"", ""},
{"hello", "hello"},
{"h%6", "h%6"},
{"%G0", "%G0"},
{"系统", "系统"},
{"Hello, 世界", "Hello, 世界"},
{string([]byte{0xff, 0xfe, 0xfd}), "<22><><EFBFBD>"},
{string([]byte{0xff}) + "Hello" + string([]byte{0xfe}) + "世界" + string([]byte{0xfd}), "<22>Hello<6C>世界<E4B896>"},
}
for _, tC := range testCases {
got := decodeGrpcMessage(encodeGrpcMessage(tC.orig))
if got != tC.want {
t.Errorf("decodeGrpcMessage(encodeGrpcMessage(%q)) = %q, want %q", tC.orig, got, tC.want)
}
}
}
const binaryValue = string(128)
func TestEncodeMetadataHeader(t *testing.T) {
for _, test := range []struct {
// input
kin string
vin string
// output
vout string
}{
{"key", "abc", "abc"},
{"KEY", "abc", "abc"},
{"key-bin", "abc", "YWJj"},
{"key-bin", binaryValue, "woA"},
} {
v := encodeMetadataHeader(test.kin, test.vin)
if !reflect.DeepEqual(v, test.vout) {
t.Fatalf("encodeMetadataHeader(%q, %q) = %q, want %q", test.kin, test.vin, v, test.vout)
}
}
}
func TestDecodeMetadataHeader(t *testing.T) {
for _, test := range []struct {
// input
kin string
vin string
// output
vout string
err error
}{
{"a", "abc", "abc", nil},
{"key-bin", "Zm9vAGJhcg==", "foo\x00bar", nil},
{"key-bin", "Zm9vAGJhcg", "foo\x00bar", nil},
{"key-bin", "woA=", binaryValue, nil},
{"a", "abc,efg", "abc,efg", nil},
} {
v, err := decodeMetadataHeader(test.kin, test.vin)
if !reflect.DeepEqual(v, test.vout) || !reflect.DeepEqual(err, test.err) {
t.Fatalf("decodeMetadataHeader(%q, %q) = %q, %v, want %q, %v", test.kin, test.vin, v, err, test.vout, test.err)
}
}
}

File diff suppressed because it is too large Load Diff