diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/BUILD | 4 | ||||
-rw-r--r-- | tools/bazel.mk | 5 | ||||
-rw-r--r-- | tools/bazeldefs/defs.bzl | 3 | ||||
-rw-r--r-- | tools/bigquery/BUILD | 3 | ||||
-rw-r--r-- | tools/bigquery/bigquery.go | 31 | ||||
-rw-r--r-- | tools/checkescape/checkescape.go | 124 | ||||
-rw-r--r-- | tools/defs.bzl | 12 | ||||
-rw-r--r-- | tools/go_marshal/gomarshal/generator.go | 8 | ||||
-rw-r--r-- | tools/go_stateify/main.go | 114 | ||||
-rw-r--r-- | tools/nogo/BUILD | 14 | ||||
-rw-r--r-- | tools/nogo/build.go | 3 | ||||
-rw-r--r-- | tools/nogo/config.go | 428 | ||||
-rw-r--r-- | tools/nogo/defs.bzl | 77 | ||||
-rwxr-xr-x | tools/nogo/gentest.sh | 47 | ||||
-rw-r--r-- | tools/nogo/matchers.go | 23 | ||||
-rw-r--r-- | tools/parsers/BUILD | 27 | ||||
-rw-r--r-- | tools/parsers/go_parser.go | 151 | ||||
-rw-r--r-- | tools/parsers/go_parser_test.go | 171 |
18 files changed, 1068 insertions, 177 deletions
diff --git a/tools/BUILD b/tools/BUILD index da83877b1..faf310676 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -5,5 +5,7 @@ package(licenses = ["notice"]) bzl_library( name = "defs_bzl", srcs = ["defs.bzl"], - visibility = ["//visibility:private"], + visibility = [ + "//:sandbox", + ], ) diff --git a/tools/bazel.mk b/tools/bazel.mk index 5e129b2ed..25575c02c 100644 --- a/tools/bazel.mk +++ b/tools/bazel.mk @@ -152,8 +152,9 @@ build_cmd = docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) sh -o pipefai build_paths = $(build_cmd) 2>&1 \ | tee /proc/self/fd/2 \ - | grep -E "^ bazel-bin/" \ - | tr -d '\r' \ + | grep " bazel-bin/" \ + | sed "s/ /\n/g" \ + | strings -n 10 \ | awk '{$$1=$$1};1' \ | xargs -n 1 -I {} sh -c "$(1)" diff --git a/tools/bazeldefs/defs.bzl b/tools/bazeldefs/defs.bzl index dad5fc3b2..cf5b1dc0d 100644 --- a/tools/bazeldefs/defs.bzl +++ b/tools/bazeldefs/defs.bzl @@ -191,3 +191,6 @@ def default_installer(): def default_net_util(): return [] # Nothing needed. + +def coreutil(): + return [] # Nothing needed. diff --git a/tools/bigquery/BUILD b/tools/bigquery/BUILD index 5748fb390..2b0062a63 100644 --- a/tools/bigquery/BUILD +++ b/tools/bigquery/BUILD @@ -6,5 +6,8 @@ go_library( name = "bigquery", testonly = 1, srcs = ["bigquery.go"], + visibility = [ + "//:sandbox", + ], deps = ["@com_google_cloud_go_bigquery//:go_default_library"], ) diff --git a/tools/bigquery/bigquery.go b/tools/bigquery/bigquery.go index 56f0dc5c9..5f1a882de 100644 --- a/tools/bigquery/bigquery.go +++ b/tools/bigquery/bigquery.go @@ -30,11 +30,20 @@ import ( // Benchmark is the top level structure of recorded benchmark data. BigQuery // will infer the schema from this. type Benchmark struct { - Name string `bq:"name"` - Timestamp time.Time `bq:"timestamp"` - Official bool `bq:"official"` - Metric []*Metric `bq:"metric"` - Metadata *Metadata `bq:"metadata"` + Name string `bq:"name"` + Condition []*Condition `bq:"condition"` + Timestamp time.Time `bq:"timestamp"` + Official bool `bq:"official"` + Metric []*Metric `bq:"metric"` + Metadata *Metadata `bq:"metadata"` +} + +// Condition represents qualifiers for the benchmark. For example: +// Get_Pid/1/real_time would have Benchmark Name "Get_Pid" with "1" +// and "real_time" parameters as conditions. +type Condition struct { + Name string `bq:"name"` + Value string `bq:"value"` } // Metric holds the actual metric data and unit information for this benchmark. @@ -79,6 +88,14 @@ func InitBigQuery(ctx context.Context, projectID, datasetID, tableID string) err return nil } +// AddCondition adds a condition to an existing Benchmark. +func (bm *Benchmark) AddCondition(name, value string) { + bm.Condition = append(bm.Condition, &Condition{ + Name: name, + Value: value, + }) +} + // AddMetric adds a metric to an existing Benchmark. func (bm *Benchmark) AddMetric(metricName, unit string, sample float64) { m := &Metric{ @@ -90,7 +107,7 @@ func (bm *Benchmark) AddMetric(metricName, unit string, sample float64) { } // NewBenchmark initializes a new benchmark. -func NewBenchmark(name string, official bool) *Benchmark { +func NewBenchmark(name string, iters int, official bool) *Benchmark { return &Benchmark{ Name: name, Timestamp: time.Now().UTC(), @@ -103,7 +120,7 @@ func NewBenchmark(name string, official bool) *Benchmark { func SendBenchmarks(ctx context.Context, benchmarks []*Benchmark, projectID, datasetID, tableID string) error { client, err := bq.NewClient(ctx, projectID) if err != nil { - return fmt.Errorf("Failed to initialize client on project: %s: %v", projectID, err) + return fmt.Errorf("failed to initialize client on project: %s: %v", projectID, err) } defer client.Close() diff --git a/tools/checkescape/checkescape.go b/tools/checkescape/checkescape.go index d98f5c3a1..523a42692 100644 --- a/tools/checkescape/checkescape.go +++ b/tools/checkescape/checkescape.go @@ -102,8 +102,8 @@ var ( // This may be set instead of Binary. Reader io.Reader - // Tool is the tool used to dump a binary. - tool = flag.String("dump_tool", "", "tool used to dump a binary") + // objdumpTool is the tool used to dump a binary. + objdumpTool = flag.String("objdump_tool", "", "tool used to dump a binary") ) // EscapeReason is an escape reason. @@ -387,7 +387,7 @@ func loadObjdump() (map[string][]string, error) { } // Construct our command. - cmd := exec.Command(*tool, args...) + cmd := exec.Command(*objdumpTool, args...) cmd.Stdin = stdin cmd.Stderr = os.Stderr out, err := cmd.StdoutPipe() @@ -398,7 +398,37 @@ func loadObjdump() (map[string][]string, error) { return nil, err } + // Identify calls by address or name. Note that this is also + // constructed dynamically below, as we encounted the addresses. + // This is because some of the functions (duffzero) may have + // jump targets in the middle of the function itself. + funcsAllowed := map[string]struct{}{ + "runtime.duffzero": struct{}{}, + "runtime.duffcopy": struct{}{}, + "runtime.racefuncenter": struct{}{}, + "runtime.gcWriteBarrier": struct{}{}, + "runtime.retpolineAX": struct{}{}, + "runtime.retpolineBP": struct{}{}, + "runtime.retpolineBX": struct{}{}, + "runtime.retpolineCX": struct{}{}, + "runtime.retpolineDI": struct{}{}, + "runtime.retpolineDX": struct{}{}, + "runtime.retpolineR10": struct{}{}, + "runtime.retpolineR11": struct{}{}, + "runtime.retpolineR12": struct{}{}, + "runtime.retpolineR13": struct{}{}, + "runtime.retpolineR14": struct{}{}, + "runtime.retpolineR15": struct{}{}, + "runtime.retpolineR8": struct{}{}, + "runtime.retpolineR9": struct{}{}, + "runtime.retpolineSI": struct{}{}, + "runtime.stackcheck": struct{}{}, + "runtime.settls": struct{}{}, + } + addrsAllowed := make(map[string]struct{}) + // Build the map. + nextFunc := "" // For funcsAllowed. m := make(map[string][]string) r := bufio.NewReader(out) NextLine: @@ -407,6 +437,19 @@ NextLine: if err != nil && err != io.EOF { return nil, err } + fields := strings.Fields(line) + + // Is this an "allowed" function definition? + if len(fields) >= 2 && fields[0] == "TEXT" { + nextFunc = strings.TrimSuffix(fields[1], "(SB)") + if _, ok := funcsAllowed[nextFunc]; !ok { + nextFunc = "" // Don't record addresses. + } + } + if nextFunc != "" && len(fields) > 2 { + // Save the given address (in hex form, as it appears). + addrsAllowed[fields[1]] = struct{}{} + } // We recognize lines corresponding to actual code (not the // symbol name or other metadata) and annotate them if they @@ -416,53 +459,31 @@ NextLine: // // Lines look like this (including the first space): // gohacks_unsafe.go:33 0xa39 488b442408 MOVQ 0x8(SP), AX - if len(line) > 0 && line[0] == ' ' { - fields := strings.Fields(line) + if len(fields) >= 5 && line[0] == ' ' { if !strings.Contains(fields[3], "CALL") { continue } - site := strings.TrimSpace(fields[0]) - var callStr string // Friendly string. - if len(fields) > 5 { - callStr = strings.Join(fields[5:], " ") - } - if len(callStr) == 0 { - // Just a raw call? is this asm? - callStr = strings.Join(fields[3:], " ") - } - - // Ignore strings containing duffzero, which is just - // used by stack allocations for types that are large - // enough to warrant Duff's device. - if strings.Contains(callStr, "runtime.duffzero") || - strings.Contains(callStr, "runtime.duffcopy") { - continue - } - - // Ignore the racefuncenter call, which is used for - // race builds. This does not escape. - if strings.Contains(callStr, "runtime.racefuncenter") { - continue - } + site := fields[0] + target := strings.TrimSuffix(fields[4], "(SB)") - // Ignore the write barriers. - if strings.Contains(callStr, "runtime.gcWriteBarrier") { + // Ignore strings containing allowed functions. + if _, ok := funcsAllowed[target]; ok { continue } - - // Ignore retpolines. - if strings.Contains(callStr, "runtime.retpoline") { + if _, ok := addrsAllowed[target]; ok { continue } - - // Ignore stack sanity check (does not split). - if strings.Contains(callStr, "runtime.stackcheck") { - continue - } - - // Ignore tls functions. - if strings.Contains(callStr, "runtime.settls") { - continue + if len(fields) > 5 { + // This may be a future relocation. Some + // objdump versions describe this differently. + // If it contains any of the functions allowed + // above as a string, we let it go. + softTarget := strings.Join(fields[5:], " ") + for name := range funcsAllowed { + if strings.Contains(softTarget, name) { + continue NextLine + } + } } // Does this exist already? @@ -471,11 +492,11 @@ NextLine: existing = make([]string, 0, 1) } for _, other := range existing { - if callStr == other { + if target == other { continue NextLine } } - existing = append(existing, callStr) + existing = append(existing, target) m[site] = existing // Update. } if err == io.EOF { @@ -483,12 +504,25 @@ NextLine: } } + // Zap any accidental false positives. + final := make(map[string][]string) + for site, calls := range m { + filteredCalls := make([]string, 0, len(calls)) + for _, call := range calls { + if _, ok := addrsAllowed[call]; ok { + continue // Omit this call. + } + filteredCalls = append(filteredCalls, call) + } + final[site] = filteredCalls + } + // Wait for the dump to finish. if err := cmd.Wait(); err != nil { return nil, err } - return m, nil + return final, nil } // poser is a type that implements Pos. diff --git a/tools/defs.bzl b/tools/defs.bzl index 60c9c9d8c..e2c72a1f6 100644 --- a/tools/defs.bzl +++ b/tools/defs.bzl @@ -7,7 +7,7 @@ change for Google-internal and bazel-compatible rules. load("//tools/go_stateify:defs.bzl", "go_stateify") load("//tools/go_marshal:defs.bzl", "go_marshal", "marshal_deps", "marshal_test_deps") -load("//tools/bazeldefs:defs.bzl", _build_test = "build_test", _bzl_library = "bzl_library", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _default_installer = "default_installer", _default_net_util = "default_net_util", _gazelle = "gazelle", _gbenchmark = "gbenchmark", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_library = "go_library", _go_path = "go_path", _go_proto_library = "go_proto_library", _go_test = "go_test", _grpcpp = "grpcpp", _gtest = "gtest", _loopback = "loopback", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar", _proto_library = "proto_library", _py_binary = "py_binary", _rbe_platform = "rbe_platform", _rbe_toolchain = "rbe_toolchain", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _vdso_linker_option = "vdso_linker_option") +load("//tools/bazeldefs:defs.bzl", _build_test = "build_test", _bzl_library = "bzl_library", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _coreutil = "coreutil", _default_installer = "default_installer", _default_net_util = "default_net_util", _gazelle = "gazelle", _gbenchmark = "gbenchmark", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_library = "go_library", _go_path = "go_path", _go_proto_library = "go_proto_library", _go_test = "go_test", _grpcpp = "grpcpp", _gtest = "gtest", _loopback = "loopback", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar", _proto_library = "proto_library", _py_binary = "py_binary", _rbe_platform = "rbe_platform", _rbe_toolchain = "rbe_toolchain", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _vdso_linker_option = "vdso_linker_option") load("//tools/bazeldefs:platforms.bzl", _default_platform = "default_platform", _platforms = "platforms") load("//tools/bazeldefs:tags.bzl", "go_suffixes") load("//tools/nogo:defs.bzl", "nogo_test") @@ -39,6 +39,7 @@ short_path = _short_path rbe_platform = _rbe_platform rbe_toolchain = _rbe_toolchain vdso_linker_option = _vdso_linker_option +coreutil = _coreutil # Platform options. default_platform = _default_platform @@ -71,7 +72,8 @@ def go_binary(name, nogo = True, pure = False, static = False, x_defs = None, ** ) nogo_test( name = name + "_nogo", - deps = [":" + name + "_nogo_library"], + srcs = kwargs.get("srcs", []), + library = ":" + name + "_nogo_library", ) def calculate_sets(srcs): @@ -202,7 +204,8 @@ def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = F if nogo: nogo_test( name = name + "_nogo", - deps = [":" + name], + srcs = all_srcs, + library = ":" + name, ) if marshal: @@ -238,7 +241,8 @@ def go_test(name, nogo = True, **kwargs): if nogo: nogo_test( name = name + "_nogo", - deps = [":" + name], + srcs = kwargs.get("srcs", []), + library = ":" + name, ) def proto_library(name, srcs, deps = None, has_services = 0, **kwargs): diff --git a/tools/go_marshal/gomarshal/generator.go b/tools/go_marshal/gomarshal/generator.go index 56fbcb5d2..4a53d25be 100644 --- a/tools/go_marshal/gomarshal/generator.go +++ b/tools/go_marshal/gomarshal/generator.go @@ -80,15 +80,15 @@ type Generator struct { func NewGenerator(srcs []string, out, outTest, outTestUnconditional, pkg string, imports []string) (*Generator, error) { f, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - return nil, fmt.Errorf("Couldn't open output file %q: %v", out, err) + return nil, fmt.Errorf("couldn't open output file %q: %w", out, err) } fTest, err := os.OpenFile(outTest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - return nil, fmt.Errorf("Couldn't open test output file %q: %v", out, err) + return nil, fmt.Errorf("couldn't open test output file %q: %w", out, err) } fTestUC, err := os.OpenFile(outTestUnconditional, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { - return nil, fmt.Errorf("Couldn't open unconditional test output file %q: %v", out, err) + return nil, fmt.Errorf("couldn't open unconditional test output file %q: %w", out, err) } g := Generator{ inputs: srcs, @@ -181,7 +181,7 @@ func (g *Generator) parse() ([]*ast.File, []*token.FileSet, error) { f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) if err != nil { // Not a valid input file? - return nil, nil, fmt.Errorf("Input %q can't be parsed: %v", path, err) + return nil, nil, fmt.Errorf("input %q can't be parsed: %w", path, err) } if debugEnabled() { diff --git a/tools/go_stateify/main.go b/tools/go_stateify/main.go index 4f6ed208a..e1de12e25 100644 --- a/tools/go_stateify/main.go +++ b/tools/go_stateify/main.go @@ -39,7 +39,7 @@ var ( ) // resolveTypeName returns a qualified type name. -func resolveTypeName(name string, typ ast.Expr) (field string, qualified string) { +func resolveTypeName(typ ast.Expr) (field string, qualified string) { for done := false; !done; { // Resolve star expressions. switch rs := typ.(type) { @@ -69,11 +69,7 @@ func resolveTypeName(name string, typ ast.Expr) (field string, qualified string) } // Figure out actual type name. - ident, ok := typ.(*ast.Ident) - if !ok { - panic(fmt.Sprintf("type not supported: %s (involves anonymous types?)", name)) - } - field = ident.Name + field = typ.(*ast.Ident).Name qualified = qualified + field return } @@ -119,7 +115,7 @@ func scanFields(ss *ast.StructType, prefix string, fn scanFunctions) { } else { // Anonymous types can't be embedded, so we don't need // to worry about providing a useful name here. - name, _ = resolveTypeName("", field.Type) + name, _ = resolveTypeName(field.Type) } // Skip _ fields. @@ -214,9 +210,6 @@ func main() { emitRegister := func(name string) { initCalls = append(initCalls, fmt.Sprintf("%sRegister((*%s)(nil))", statePrefix, name)) } - emitZeroCheck := func(name string) { - fmt.Fprintf(outputFile, " if !%sIsZeroValue(&x.%s) { %sFailf(\"%s is %%#v, expected zero\", &x.%s) }\n", statePrefix, name, statePrefix, name, name) - } // Automated warning. fmt.Fprint(outputFile, "// automatically generated by stateify.\n\n") @@ -265,52 +258,39 @@ func main() { } type method struct { - receiver string - name string + typeName string + methodName string } - // Search for and add all methods with a pointer receiver and no other - // arguments to a set. We support auto-detecting the existence of - // several different methods with this signature. - simpleMethods := map[method]struct{}{} + // Search for and add all method to a set. We auto-detecting several + // different methods (and insert them if we don't find them, in order + // to ensure that expectations match reality). + // + // While we do this, figure out the right receiver name. If there are + // multiple distinct receivers, then we will just pick the last one. + simpleMethods := make(map[method]struct{}) + receiverNames := make(map[string]string) for _, f := range files { - // Go over all functions. for _, decl := range f.Decls { d, ok := decl.(*ast.FuncDecl) if !ok { continue } - if d.Name == nil || d.Recv == nil || d.Type == nil { + if d.Recv == nil || len(d.Recv.List) != 1 { // Not a named method. continue } - if len(d.Recv.List) != 1 { - // Wrong number of receivers? - continue - } - if d.Type.Params != nil && len(d.Type.Params.List) != 0 { - // Has argument(s). - continue - } - if d.Type.Results != nil && len(d.Type.Results.List) != 0 { - // Has return(s). - continue - } - pt, ok := d.Recv.List[0].Type.(*ast.StarExpr) - if !ok { - // Not a pointer receiver. - continue + // Save the method and the receiver. + name, _ := resolveTypeName(d.Recv.List[0].Type) + simpleMethods[method{ + typeName: name, + methodName: d.Name.Name, + }] = struct{}{} + if len(d.Recv.List[0].Names) > 0 { + receiverNames[name] = d.Recv.List[0].Names[0].Name } - - t, ok := pt.X.(*ast.Ident) - if !ok { - // This shouldn't happen with valid Go. - continue - } - - simpleMethods[method{t.Name, d.Name.Name}] = struct{}{} } } @@ -349,6 +329,11 @@ func main() { for _, gs := range d.Specs { ts := gs.(*ast.TypeSpec) + recv, ok := receiverNames[ts.Name.Name] + if !ok { + // Maybe no methods were defined? + recv = strings.ToLower(ts.Name.Name[:1]) + } switch x := ts.Type.(type) { case *ast.StructType: maybeEmitImports() @@ -365,29 +350,32 @@ func main() { emitField(name) } emitLoadValue := func(name, typName string) { - fmt.Fprintf(outputFile, " m.LoadValue(%d, new(%s), func(y interface{}) { x.load%s(y.(%s)) })\n", fields[name], typName, camelCased(name), typName) + fmt.Fprintf(outputFile, " stateSourceObject.LoadValue(%d, new(%s), func(y interface{}) { %s.load%s(y.(%s)) })\n", fields[name], typName, recv, camelCased(name), typName) } emitLoad := func(name string) { - fmt.Fprintf(outputFile, " m.Load(%d, &x.%s)\n", fields[name], name) + fmt.Fprintf(outputFile, " stateSourceObject.Load(%d, &%s.%s)\n", fields[name], recv, name) } emitLoadWait := func(name string) { - fmt.Fprintf(outputFile, " m.LoadWait(%d, &x.%s)\n", fields[name], name) + fmt.Fprintf(outputFile, " stateSourceObject.LoadWait(%d, &%s.%s)\n", fields[name], recv, name) } emitSaveValue := func(name, typName string) { - fmt.Fprintf(outputFile, " var %s %s = x.save%s()\n", name, typName, camelCased(name)) - fmt.Fprintf(outputFile, " m.SaveValue(%d, %s)\n", fields[name], name) + fmt.Fprintf(outputFile, " var %sValue %s = %s.save%s()\n", name, typName, recv, camelCased(name)) + fmt.Fprintf(outputFile, " stateSinkObject.SaveValue(%d, %sValue)\n", fields[name], name) } emitSave := func(name string) { - fmt.Fprintf(outputFile, " m.Save(%d, &x.%s)\n", fields[name], name) + fmt.Fprintf(outputFile, " stateSinkObject.Save(%d, &%s.%s)\n", fields[name], recv, name) + } + emitZeroCheck := func(name string) { + fmt.Fprintf(outputFile, " if !%sIsZeroValue(&%s.%s) { %sFailf(\"%s is %%#v, expected zero\", &%s.%s) }\n", statePrefix, recv, name, statePrefix, name, recv, name) } // Generate the type name method. - fmt.Fprintf(outputFile, "func (x *%s) StateTypeName() string {\n", ts.Name.Name) + fmt.Fprintf(outputFile, "func (%s *%s) StateTypeName() string {\n", recv, ts.Name.Name) fmt.Fprintf(outputFile, " return \"%s.%s\"\n", *fullPkg, ts.Name.Name) fmt.Fprintf(outputFile, "}\n\n") // Generate the fields method. - fmt.Fprintf(outputFile, "func (x *%s) StateFields() []string {\n", ts.Name.Name) + fmt.Fprintf(outputFile, "func (%s *%s) StateFields() []string {\n", recv, ts.Name.Name) fmt.Fprintf(outputFile, " return []string{\n") scanFields(x, "", scanFunctions{ normal: emitField, @@ -401,8 +389,11 @@ func main() { // the code from compiling if a custom beforeSave was defined in a // file not provided to this binary and prevents inherited methods // from being called multiple times by overriding them. - if _, ok := simpleMethods[method{ts.Name.Name, "beforeSave"}]; !ok && generateSaverLoader { - fmt.Fprintf(outputFile, "func (x *%s) beforeSave() {}\n\n", ts.Name.Name) + if _, ok := simpleMethods[method{ + typeName: ts.Name.Name, + methodName: "beforeSave", + }]; !ok && generateSaverLoader { + fmt.Fprintf(outputFile, "func (%s *%s) beforeSave() {}\n\n", recv, ts.Name.Name) } // Generate the save method. @@ -412,8 +403,8 @@ func main() { // on this specific behavior, but the ability to specify slots // allows a manual implementation to be order-dependent. if generateSaverLoader { - fmt.Fprintf(outputFile, "func (x *%s) StateSave(m %sSink) {\n", ts.Name.Name, statePrefix) - fmt.Fprintf(outputFile, " x.beforeSave()\n") + fmt.Fprintf(outputFile, "func (%s *%s) StateSave(stateSinkObject %sSink) {\n", recv, ts.Name.Name, statePrefix) + fmt.Fprintf(outputFile, " %s.beforeSave()\n", recv) scanFields(x, "", scanFunctions{zerovalue: emitZeroCheck}) scanFields(x, "", scanFunctions{value: emitSaveValue}) scanFields(x, "", scanFunctions{normal: emitSave, wait: emitSave}) @@ -422,16 +413,19 @@ func main() { // Define afterLoad if a definition was not found. We do this for // the same reason that we do it for beforeSave. - _, hasAfterLoad := simpleMethods[method{ts.Name.Name, "afterLoad"}] + _, hasAfterLoad := simpleMethods[method{ + typeName: ts.Name.Name, + methodName: "afterLoad", + }] if !hasAfterLoad && generateSaverLoader { - fmt.Fprintf(outputFile, "func (x *%s) afterLoad() {}\n\n", ts.Name.Name) + fmt.Fprintf(outputFile, "func (%s *%s) afterLoad() {}\n\n", recv, ts.Name.Name) } // Generate the load method. // // N.B. See the comment above for the save method. if generateSaverLoader { - fmt.Fprintf(outputFile, "func (x *%s) StateLoad(m %sSource) {\n", ts.Name.Name, statePrefix) + fmt.Fprintf(outputFile, "func (%s *%s) StateLoad(stateSourceObject %sSource) {\n", recv, ts.Name.Name, statePrefix) scanFields(x, "", scanFunctions{normal: emitLoad, wait: emitLoadWait}) scanFields(x, "", scanFunctions{value: emitLoadValue}) if hasAfterLoad { @@ -439,7 +433,7 @@ func main() { // AfterLoad is called, the object encodes a dependency on // referred objects (i.e. fields). This means that afterLoad // will not be called until the other afterLoads are called. - fmt.Fprintf(outputFile, " m.AfterLoad(x.afterLoad)\n") + fmt.Fprintf(outputFile, " stateSourceObject.AfterLoad(%s.afterLoad)\n", recv) } fmt.Fprintf(outputFile, "}\n\n") } @@ -451,10 +445,10 @@ func main() { maybeEmitImports() // Generate the info methods. - fmt.Fprintf(outputFile, "func (x *%s) StateTypeName() string {\n", ts.Name.Name) + fmt.Fprintf(outputFile, "func (%s *%s) StateTypeName() string {\n", recv, ts.Name.Name) fmt.Fprintf(outputFile, " return \"%s.%s\"\n", *fullPkg, ts.Name.Name) fmt.Fprintf(outputFile, "}\n\n") - fmt.Fprintf(outputFile, "func (x *%s) StateFields() []string {\n", ts.Name.Name) + fmt.Fprintf(outputFile, "func (%s *%s) StateFields() []string {\n", recv, ts.Name.Name) fmt.Fprintf(outputFile, " return nil\n") fmt.Fprintf(outputFile, "}\n\n") diff --git a/tools/nogo/BUILD b/tools/nogo/BUILD index 9f1fcd9c7..dd4b46f58 100644 --- a/tools/nogo/BUILD +++ b/tools/nogo/BUILD @@ -1,10 +1,10 @@ load("//tools:defs.bzl", "bzl_library", "go_library") -load("//tools/nogo:defs.bzl", "nogo_dump_tool", "nogo_stdlib") +load("//tools/nogo:defs.bzl", "nogo_objdump_tool", "nogo_stdlib") package(licenses = ["notice"]) -nogo_dump_tool( - name = "dump_tool", +nogo_objdump_tool( + name = "objdump_tool", visibility = ["//visibility:public"], ) @@ -13,6 +13,12 @@ nogo_stdlib( visibility = ["//visibility:public"], ) +sh_binary( + name = "gentest", + srcs = ["gentest.sh"], + visibility = ["//visibility:public"], +) + go_library( name = "nogo", srcs = [ @@ -27,6 +33,8 @@ go_library( deps = [ "//tools/checkescape", "//tools/checkunsafe", + "@co_honnef_go_tools//staticcheck:go_default_library", + "@co_honnef_go_tools//stylecheck:go_default_library", "@org_golang_x_tools//go/analysis:go_tool_library", "@org_golang_x_tools//go/analysis/internal/facts:go_tool_library", "@org_golang_x_tools//go/analysis/passes/asmdecl:go_tool_library", diff --git a/tools/nogo/build.go b/tools/nogo/build.go index 39c2ae418..55d34760f 100644 --- a/tools/nogo/build.go +++ b/tools/nogo/build.go @@ -29,6 +29,9 @@ var ( // internalDefault is applied when no paths are provided. internalDefault = fmt.Sprintf("%s/.*", notPath("external")) + // generatedPrefix is a regex for generated files. + generatedPrefix = "^(.*/)?(bazel-genfiles|bazel-out|bazel-bin)/" + // externalPrefix is external workspace packages. externalPrefix = "^external/" ) diff --git a/tools/nogo/config.go b/tools/nogo/config.go index cfe7b4aa4..8079618ab 100644 --- a/tools/nogo/config.go +++ b/tools/nogo/config.go @@ -41,6 +41,8 @@ import ( "golang.org/x/tools/go/analysis/passes/unreachable" "golang.org/x/tools/go/analysis/passes/unsafeptr" "golang.org/x/tools/go/analysis/passes/unusedresult" + "honnef.co/go/tools/staticcheck" + "honnef.co/go/tools/stylecheck" "gvisor.dev/gvisor/tools/checkescape" "gvisor.dev/gvisor/tools/checkunsafe" @@ -123,6 +125,432 @@ var analyzerConfig = map[*analysis.Analyzer]matcher{ checkunsafe.Analyzer: internalMatches(), } +func init() { + staticMatcher := and( + // Only match internal, non-generated files. + internalMatches(), + generatedExcluded(), + + // We use ALL_CAPS for system definitions, + // which are common enough in the code base + // that we shouldn't annotate exceptions. + // + // Same story for underscores. + resultExcluded([]string{ + "should not use ALL_CAPS in Go names", + "should not use underscores in Go names", + }), + + // Exclude existing matches. + internalExcluded( + "pkg/abi/linux/fuse.go:22", + "pkg/abi/linux/fuse.go:25", + "pkg/abi/linux/socket.go:113", + "pkg/abi/linux/tty.go:73", + "pkg/bpf/decoder.go:112", + "pkg/cpuid/cpuid_x86.go:675", + "pkg/eventchannel/event.go:193", + "pkg/eventchannel/event.go:27", + "pkg/eventchannel/event_test.go:22", + "pkg/eventchannel/rate.go:19", + "pkg/gohacks/gohacks_unsafe.go:33", + "pkg/log/json.go:30", + "pkg/log/log.go:359", + "pkg/merkletree/merkletree.go:230", + "pkg/merkletree/merkletree.go:243", + "pkg/merkletree/merkletree.go:249", + "pkg/merkletree/merkletree.go:266", + "pkg/merkletree/merkletree.go:355", + "pkg/merkletree/merkletree.go:369", + "pkg/metric/metric_test.go:20", + "pkg/p9/p9test/client_test.go:687", + "pkg/p9/transport_test.go:196", + "pkg/pool/pool.go:15", + "pkg/refs/refcounter.go:510", + "pkg/refs/refcounter_test.go:169", + "pkg/refs_vfs2/refs.go:16", + "pkg/safemem/block_unsafe.go:89", + "pkg/seccomp/seccomp.go:82", + "pkg/segment/test/set_functions.go:15", + "pkg/sentry/arch/signal.go:166", + "pkg/sentry/arch/signal.go:171", + "pkg/sentry/control/pprof.go:196", + "pkg/sentry/devices/memdev/full.go:58", + "pkg/sentry/devices/memdev/null.go:59", + "pkg/sentry/devices/memdev/random.go:68", + "pkg/sentry/devices/memdev/zero.go:86", + "pkg/sentry/fdimport/fdimport.go:15", + "pkg/sentry/fs/attr.go:257", + "pkg/sentry/fsbridge/fs.go:116", + "pkg/sentry/fsbridge/vfs.go:124", + "pkg/sentry/fsbridge/vfs.go:70", + "pkg/sentry/fs/copy_up.go:365", + "pkg/sentry/fs/copy_up_test.go:65", + "pkg/sentry/fs/dev/net_tun.go:161", + "pkg/sentry/fs/dev/net_tun.go:63", + "pkg/sentry/fs/dev/null.go:97", + "pkg/sentry/fs/dirent_cache.go:64", + "pkg/sentry/fs/file_overlay.go:327", + "pkg/sentry/fs/file_overlay.go:524", + "pkg/sentry/fs/filetest/filetest.go:55", + "pkg/sentry/fs/filetest/filetest.go:60", + "pkg/sentry/fs/fs.go:77", + "pkg/sentry/fs/fsutil/file.go:290", + "pkg/sentry/fs/fsutil/file.go:346", + "pkg/sentry/fs/fsutil/host_file_mapper.go:105", + "pkg/sentry/fs/fsutil/inode_cached.go:676", + "pkg/sentry/fs/fsutil/inode_cached.go:772", + "pkg/sentry/fs/gofer/attr.go:120", + "pkg/sentry/fs/gofer/fifo.go:33", + "pkg/sentry/fs/gofer/inode.go:410", + "pkg/sentry/fsimpl/devpts/devpts.go:110", + "pkg/sentry/fsimpl/devpts/devpts.go:246", + "pkg/sentry/fsimpl/devpts/devpts.go:50", + "pkg/sentry/fsimpl/devpts/master.go:110", + "pkg/sentry/fsimpl/devpts/master.go:55", + "pkg/sentry/fsimpl/devpts/replica.go:113", + "pkg/sentry/fsimpl/devpts/replica.go:57", + "pkg/sentry/fsimpl/devtmpfs/devtmpfs.go:54", + "pkg/sentry/fsimpl/ext/disklayout/superblock_64.go:97", + "pkg/sentry/fsimpl/ext/disklayout/superblock_old.go:92", + "pkg/sentry/fsimpl/ext/disklayout/block_group_32.go:44", + "pkg/sentry/fsimpl/ext/disklayout/inode_new.go:91", + "pkg/sentry/fsimpl/ext/disklayout/inode_old.go:93", + "pkg/sentry/fsimpl/ext/disklayout/superblock_32.go:66", + "pkg/sentry/fsimpl/ext/disklayout/block_group_64.go:53", + "pkg/sentry/fsimpl/eventfd/eventfd.go:268", + "pkg/sentry/fsimpl/ext/directory.go:163", + "pkg/sentry/fsimpl/ext/directory.go:164", + "pkg/sentry/fsimpl/ext/extent_file.go:142", + "pkg/sentry/fsimpl/ext/extent_file.go:143", + "pkg/sentry/fsimpl/ext/ext.go:105", + "pkg/sentry/fsimpl/ext/filesystem.go:287", + "pkg/sentry/fsimpl/ext/regular_file.go:153", + "pkg/sentry/fsimpl/ext/symlink.go:113", + "pkg/sentry/fsimpl/fuse/connection_control.go:194", + "pkg/sentry/fsimpl/fuse/dev.go:387", + "pkg/sentry/fsimpl/fuse/dev_test.go:318", + "pkg/sentry/fsimpl/fuse/fusefs.go:102", + "pkg/sentry/fsimpl/fuse/read_write.go:129", + "pkg/sentry/fsimpl/fuse/request_response.go:71", + "pkg/sentry/fsimpl/gofer/directory.go:135", + "pkg/sentry/fsimpl/gofer/filesystem.go:679", + "pkg/sentry/fsimpl/gofer/gofer.go:1694", + "pkg/sentry/fsimpl/gofer/gofer.go:276", + "pkg/sentry/fsimpl/gofer/regular_file.go:81", + "pkg/sentry/fsimpl/gofer/special_file.go:141", + "pkg/sentry/fsimpl/host/host.go:184", + "pkg/sentry/fsimpl/kernfs/dynamic_bytes_file.go:50", + "pkg/sentry/fsimpl/kernfs/dynamic_bytes_file.go:90", + "pkg/sentry/fsimpl/kernfs/fd_impl_util.go:273", + "pkg/sentry/fsimpl/kernfs/filesystem.go:247", + "pkg/sentry/fsimpl/kernfs/inode_impl_util.go:320", + "pkg/sentry/fsimpl/kernfs/inode_impl_util.go:497", + "pkg/sentry/fsimpl/kernfs/synthetic_directory.go:52", + "pkg/sentry/fsimpl/overlay/directory.go:119", + "pkg/sentry/fsimpl/overlay/filesystem.go:527", + "pkg/sentry/fsimpl/overlay/non_directory.go:152", + "pkg/sentry/fsimpl/overlay/overlay.go:115", + "pkg/sentry/fsimpl/overlay/overlay.go:719", + "pkg/sentry/fsimpl/pipefs/pipefs.go:74", + "pkg/sentry/fsimpl/proc/filesystem.go:52", + "pkg/sentry/fsimpl/proc/filesystem.go:81", + "pkg/sentry/fsimpl/proc/subtasks.go:126", + "pkg/sentry/fsimpl/proc/subtasks.go:189", + "pkg/sentry/fsimpl/proc/task_fds.go:168", + "pkg/sentry/fsimpl/proc/task_fds.go:228", + "pkg/sentry/fsimpl/proc/task_fds.go:301", + "pkg/sentry/fsimpl/proc/task_fds.go:318", + "pkg/sentry/fsimpl/proc/task_fds.go:67", + "pkg/sentry/fsimpl/proc/task_files.go:112", + "pkg/sentry/fsimpl/proc/task_files.go:158", + "pkg/sentry/fsimpl/proc/task_files.go:259", + "pkg/sentry/fsimpl/proc/task_files.go:285", + "pkg/sentry/fsimpl/proc/task_files.go:305", + "pkg/sentry/fsimpl/proc/task_files.go:384", + "pkg/sentry/fsimpl/proc/task_files.go:403", + "pkg/sentry/fsimpl/proc/task_files.go:428", + "pkg/sentry/fsimpl/proc/task_files.go:691", + "pkg/sentry/fsimpl/proc/task_files.go:770", + "pkg/sentry/fsimpl/proc/task_files.go:797", + "pkg/sentry/fsimpl/proc/task_files.go:828", + "pkg/sentry/fsimpl/proc/task_files.go:879", + "pkg/sentry/fsimpl/proc/task_files.go:910", + "pkg/sentry/fsimpl/proc/task_files.go:961", + "pkg/sentry/fsimpl/proc/task.go:127", + "pkg/sentry/fsimpl/proc/task.go:193", + "pkg/sentry/fsimpl/proc/task_net.go:134", + "pkg/sentry/fsimpl/proc/task_net.go:475", + "pkg/sentry/fsimpl/proc/task_net.go:491", + "pkg/sentry/fsimpl/proc/task_net.go:508", + "pkg/sentry/fsimpl/proc/task_net.go:665", + "pkg/sentry/fsimpl/proc/task_net.go:715", + "pkg/sentry/fsimpl/proc/task_net.go:779", + "pkg/sentry/fsimpl/proc/tasks_files.go:113", + "pkg/sentry/fsimpl/proc/tasks_files.go:388", + "pkg/sentry/fsimpl/proc/tasks.go:232", + "pkg/sentry/fsimpl/proc/tasks_sys.go:145", + "pkg/sentry/fsimpl/proc/tasks_sys.go:181", + "pkg/sentry/fsimpl/proc/tasks_sys.go:239", + "pkg/sentry/fsimpl/proc/tasks_sys.go:291", + "pkg/sentry/fsimpl/proc/tasks_sys.go:375", + "pkg/sentry/fsimpl/signalfd/signalfd.go:124", + "pkg/sentry/fsimpl/signalfd/signalfd.go:15", + "pkg/sentry/fsimpl/signalfd/signalfd.go:126", + "pkg/sentry/fsimpl/sockfs/sockfs.go:36", + "pkg/sentry/fsimpl/sockfs/sockfs.go:79", + "pkg/sentry/fsimpl/sys/kcov.go:49", + "pkg/sentry/fsimpl/sys/kcov.go:99", + "pkg/sentry/fsimpl/sys/sys.go:118", + "pkg/sentry/fsimpl/sys/sys.go:56", + "pkg/sentry/fsimpl/testutil/testutil.go:257", + "pkg/sentry/fsimpl/testutil/testutil.go:260", + "pkg/sentry/fsimpl/timerfd/timerfd.go:87", + "pkg/sentry/fsimpl/tmpfs/directory.go:112", + "pkg/sentry/fsimpl/tmpfs/filesystem.go:195", + "pkg/sentry/fsimpl/tmpfs/regular_file.go:226", + "pkg/sentry/fsimpl/tmpfs/regular_file.go:346", + "pkg/sentry/fsimpl/tmpfs/tmpfs.go:103", + "pkg/sentry/fsimpl/tmpfs/tmpfs.go:733", + "pkg/sentry/fsimpl/verity/filesystem.go:490", + "pkg/sentry/fsimpl/verity/verity.go:156", + "pkg/sentry/fsimpl/verity/verity.go:629", + "pkg/sentry/fsimpl/verity/verity.go:672", + "pkg/sentry/fs/mount.go:162", + "pkg/sentry/fs/mount.go:256", + "pkg/sentry/fs/mount_overlay.go:144", + "pkg/sentry/fs/mounts.go:432", + "pkg/sentry/fs/proc/exec_args.go:104", + "pkg/sentry/fs/proc/exec_args.go:73", + "pkg/sentry/fs/proc/fds.go:269", + "pkg/sentry/fs/proc/loadavg.go:33", + "pkg/sentry/fs/proc/meminfo.go:39", + "pkg/sentry/fs/proc/mounts.go:193", + "pkg/sentry/fs/proc/mounts.go:84", + "pkg/sentry/fs/proc/net.go:125", + "pkg/sentry/fs/proc/proc.go:146", + "pkg/sentry/fs/proc/proc.go:204", + "pkg/sentry/fs/proc/seqfile/seqfile.go:210", + "pkg/sentry/fs/proc/sys.go:146", + "pkg/sentry/fs/proc/sys.go:43", + "pkg/sentry/fs/proc/sys_net.go:113", + "pkg/sentry/fs/proc/sys_net.go:205", + "pkg/sentry/fs/proc/sys_net.go:233", + "pkg/sentry/fs/proc/sys_net.go:307", + "pkg/sentry/fs/proc/sys_net.go:335", + "pkg/sentry/fs/proc/sys_net.go:446", + "pkg/sentry/fs/proc/sys_net.go:456", + "pkg/sentry/fs/proc/sys_net.go:89", + "pkg/sentry/fs/proc/task.go:170", + "pkg/sentry/fs/proc/task.go:322", + "pkg/sentry/fs/proc/task.go:427", + "pkg/sentry/fs/proc/task.go:467", + "pkg/sentry/fs/proc/task.go:500", + "pkg/sentry/fs/proc/task.go:784", + "pkg/sentry/fs/proc/task.go:839", + "pkg/sentry/fs/proc/task.go:920", + "pkg/sentry/fs/proc/uid_gid_map.go:108", + "pkg/sentry/fs/proc/uid_gid_map.go:79", + "pkg/sentry/fs/proc/uptime.go:75", + "pkg/sentry/fs/ramfs/dir.go:447", + "pkg/sentry/fs/tmpfs/inode_file.go:436", + "pkg/sentry/fs/tmpfs/inode_file.go:537", + "pkg/sentry/fs/tty/dir.go:313", + "pkg/sentry/fs/tty/master.go:131", + "pkg/sentry/fs/tty/master.go:91", + "pkg/sentry/fs/tty/replica.go:116", + "pkg/sentry/fs/tty/replica.go:88", + "pkg/sentry/kernel/auth/id_map.go:269", + "pkg/sentry/kernel/fasync/fasync.go:67", + "pkg/sentry/kernel/kcov.go:209", + "pkg/sentry/kernel/kcov.go:223", + "pkg/sentry/kernel/kernel.go:343", + "pkg/sentry/kernel/kernel.go:368", + "pkg/sentry/kernel/pipe/node_test.go:112", + "pkg/sentry/kernel/pipe/node_test.go:119", + "pkg/sentry/kernel/pipe/node_test.go:130", + "pkg/sentry/kernel/pipe/node_test.go:137", + "pkg/sentry/kernel/pipe/node_test.go:149", + "pkg/sentry/kernel/pipe/node_test.go:150", + "pkg/sentry/kernel/pipe/node_test.go:158", + "pkg/sentry/kernel/pipe/node_test.go:174", + "pkg/sentry/kernel/pipe/node_test.go:180", + "pkg/sentry/kernel/pipe/node_test.go:193", + "pkg/sentry/kernel/pipe/node_test.go:202", + "pkg/sentry/kernel/pipe/node_test.go:205", + "pkg/sentry/kernel/pipe/node_test.go:216", + "pkg/sentry/kernel/pipe/node_test.go:219", + "pkg/sentry/kernel/pipe/node_test.go:271", + "pkg/sentry/kernel/pipe/node_test.go:290", + "pkg/sentry/kernel/pipe/pipe_test.go:93", + "pkg/sentry/kernel/pipe/reader_writer.go:65", + "pkg/sentry/kernel/posixtimer.go:157", + "pkg/sentry/kernel/ptrace.go:218", + "pkg/sentry/kernel/semaphore/semaphore.go:323", + "pkg/sentry/kernel/sessions.go:123", + "pkg/sentry/kernel/sessions.go:508", + "pkg/sentry/kernel/signal_handlers.go:57", + "pkg/sentry/kernel/task_context.go:72", + "pkg/sentry/kernel/task_exit.go:67", + "pkg/sentry/kernel/task_sched.go:255", + "pkg/sentry/kernel/task_sched.go:280", + "pkg/sentry/kernel/task_sched.go:323", + "pkg/sentry/kernel/task_stop.go:192", + "pkg/sentry/kernel/thread_group.go:530", + "pkg/sentry/kernel/timekeeper.go:316", + "pkg/sentry/kernel/vdso.go:106", + "pkg/sentry/kernel/vdso.go:118", + "pkg/sentry/memmap/memmap.go:103", + "pkg/sentry/memmap/memmap.go:163", + "pkg/sentry/mm/address_space.go:42", + "pkg/sentry/mm/address_space.go:42", + "pkg/sentry/mm/aio_context.go:208", + "pkg/sentry/mm/aio_context.go:288", + "pkg/sentry/mm/pma.go:683", + "pkg/sentry/mm/special_mappable.go:80", + "pkg/sentry/platform/systrap/subprocess.go:370", + "pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go:124", + "pkg/sentry/socket/control/control.go:260", + "pkg/sentry/socket/control/control.go:94", + "pkg/sentry/socket/control/control_vfs2.go:37", + "pkg/sentry/socket/hostinet/stack.go:433", + "pkg/sentry/socket/hostinet/stack.go:438", + "pkg/sentry/socket/hostinet/stack.go:444", + "pkg/sentry/socket/hostinet/stack.go:460", + "pkg/sentry/socket/netfilter/tcp_matcher.go:74", + "pkg/sentry/socket/netfilter/udp_matcher.go:71", + "pkg/sentry/socket/netlink/route/protocol.go:38", + "pkg/sentry/socket/socket.go:332", + "pkg/sentry/socket/unix/transport/connectioned.go:394", + "pkg/sentry/socket/unix/transport/connectionless.go:152", + "pkg/sentry/socket/unix/transport/unix.go:436", + "pkg/sentry/socket/unix/transport/unix.go:490", + "pkg/sentry/socket/unix/transport/unix.go:685", + "pkg/sentry/socket/unix/transport/unix.go:795", + "pkg/sentry/syscalls/linux/sys_sem.go:62", + "pkg/sentry/syscalls/linux/sys_time.go:189", + "pkg/sentry/usage/cpu.go:42", + "pkg/sentry/vfs/anonfs.go:302", + "pkg/sentry/vfs/anonfs.go:99", + "pkg/sentry/vfs/dentry.go:214", + "pkg/sentry/vfs/epoll.go:168", + "pkg/sentry/vfs/epoll.go:314", + "pkg/sentry/vfs/file_description.go:549", + "pkg/sentry/vfs/file_description_impl_util.go:304", + "pkg/sentry/vfs/file_description_impl_util.go:412", + "pkg/sentry/vfs/filesystem.go:76", + "pkg/sentry/vfs/lock.go:15", + "pkg/sentry/vfs/lock.go:47", + "pkg/sentry/vfs/memxattr/xattr.go:37", + "pkg/sentry/vfs/mount.go:510", + "pkg/sentry/vfs/mount.go:667", + "pkg/sentry/vfs/mount_test.go:106", + "pkg/sentry/vfs/mount_test.go:160", + "pkg/sentry/vfs/mount_test.go:215", + "pkg/sentry/vfs/mount_unsafe.go:153", + "pkg/sentry/vfs/resolving_path.go:228", + "pkg/sentry/vfs/vfs.go:897", + "pkg/shim/runsc/runsc.go:16", + "pkg/shim/runsc/utils.go:16", + "pkg/shim/v1/proc/deleted_state.go:16", + "pkg/shim/v1/proc/exec.go:16", + "pkg/shim/v1/proc/exec_state.go:16", + "pkg/shim/v1/proc/init.go:16", + "pkg/shim/v1/proc/init_state.go:16", + "pkg/shim/v1/proc/io.go:16", + "pkg/shim/v1/proc/process.go:16", + "pkg/shim/v1/proc/types.go:16", + "pkg/shim/v1/proc/utils.go:16", + "pkg/shim/v1/shim/api.go:16", + "pkg/shim/v1/shim/platform.go:16", + "pkg/shim/v1/shim/service.go:16", + "pkg/shim/v1/utils/annotations.go:15", + "pkg/shim/v1/utils/utils.go:15", + "pkg/shim/v1/utils/volumes.go:15", + "pkg/shim/v2/api.go:16", + "pkg/shim/v2/epoll.go:18", + "pkg/shim/v2/options/options.go:15", + "pkg/shim/v2/options/options.go:24", + "pkg/shim/v2/options/options.go:26", + "pkg/shim/v2/runtimeoptions/runtimeoptions.go:16", + "pkg/shim/v2/runtimeoptions/runtimeoptions_test.go:22", + "pkg/shim/v2/service.go:15", + "pkg/shim/v2/service_linux.go:18", + "pkg/state/tests/integer_test.go:23", + "pkg/state/tests/integer_test.go:28", + "pkg/sync/rwmutex_test.go:105", + "pkg/syserr/host_linux.go:35", + "pkg/tcpip/adapters/gonet/gonet_test.go:144", + "pkg/tcpip/adapters/gonet/gonet_test.go:415", + "pkg/tcpip/adapters/gonet/gonet_test.go:99", + "pkg/tcpip/buffer/view.go:238", + "pkg/tcpip/buffer/view.go:238", + "pkg/tcpip/buffer/view.go:246", + "pkg/tcpip/header/tcp.go:151", + "pkg/tcpip/link/sharedmem/pipe/pipe_test.go:493", + "pkg/tcpip/stack/iptables.go:293", + "pkg/tcpip/stack/iptables_types.go:277", + "pkg/tcpip/stack/stack.go:553", + "pkg/tcpip/stack/transport_test.go:30", + "pkg/tcpip/transport/packet/endpoint.go:126", + "pkg/tcpip/transport/raw/endpoint.go:145", + "pkg/tcpip/transport/tcp/sack_scoreboard.go:167", + "pkg/unet/unet_test.go:634", + "pkg/unet/unet_test.go:662", + "pkg/unet/unet_test.go:703", + "pkg/unet/unet_test.go:98", + "pkg/usermem/addr.go:34", + "pkg/usermem/usermem.go:171", + "pkg/usermem/usermem.go:170", + "runsc/boot/compat.go:22", + "runsc/boot/compat.go:56", + "runsc/boot/loader.go:1115", + "runsc/boot/loader.go:1120", + "runsc/cmd/checkpoint.go:151", + "runsc/config/flags.go:32", + "runsc/container/container.go:641", + "runsc/container/container.go:988", + "runsc/specutils/specutils.go:172", + "runsc/specutils/specutils.go:428", + "runsc/specutils/specutils.go:436", + "runsc/specutils/specutils.go:442", + "runsc/specutils/specutils.go:447", + "runsc/specutils/specutils.go:454", + "test/cmd/test_app/fds.go:171", + "test/iptables/filter_output.go:251", + "test/packetimpact/testbench/connections.go:77", + "tools/bigquery/bigquery.go:106", + "tools/checkescape/test1/test1.go:108", + "tools/checkescape/test1/test1.go:122", + "tools/checkescape/test1/test1.go:137", + "tools/checkescape/test1/test1.go:151", + "tools/checkescape/test1/test1.go:170", + "tools/checkescape/test1/test1.go:39", + "tools/checkescape/test1/test1.go:45", + "tools/checkescape/test1/test1.go:50", + "tools/checkescape/test1/test1.go:64", + "tools/checkescape/test1/test1.go:80", + "tools/checkescape/test1/test1.go:94", + "tools/go_generics/imports.go:51", + "tools/go_generics/imports.go:75", + "tools/go_marshal/gomarshal/generator.go:177", + "tools/go_marshal/gomarshal/generator.go:81", + "tools/go_marshal/gomarshal/generator.go:85", + "tools/go_marshal/test/escape/escape.go:15", + "tools/go_marshal/test/test.go:164", + ), + ) + + // Add all staticcheck analyzers; internal only. + for _, a := range staticcheck.Analyzers { + analyzerConfig[a] = staticMatcher + } + // Add all stylecheck analyzers; internal only. + for _, a := range stylecheck.Analyzers { + analyzerConfig[a] = staticMatcher + } +} + var escapesConfig = map[*analysis.Analyzer]matcher{ // Informational only: include all packages. checkescape.EscapeAnalyzer: alwaysMatches(), diff --git a/tools/nogo/defs.bzl b/tools/nogo/defs.bzl index 480438047..c6fcfd402 100644 --- a/tools/nogo/defs.bzl +++ b/tools/nogo/defs.bzl @@ -2,8 +2,7 @@ load("//tools/bazeldefs:defs.bzl", "go_context", "go_importpath", "go_rule", "go_test_library") -def _nogo_dump_tool_impl(ctx): - # Extract the Go context. +def _nogo_objdump_tool_impl(ctx): go_ctx = go_context(ctx) # Construct the magic dump command. @@ -40,9 +39,9 @@ def _nogo_dump_tool_impl(ctx): executable = dumper, )] -nogo_dump_tool = go_rule( +nogo_objdump_tool = go_rule( rule, - implementation = _nogo_dump_tool_impl, + implementation = _nogo_objdump_tool_impl, ) # NogoStdlibInfo is the set of standard library facts. @@ -55,7 +54,6 @@ NogoStdlibInfo = provider( ) def _nogo_stdlib_impl(ctx): - # Extract the Go context. go_ctx = go_context(ctx) # Build the standard library facts. @@ -72,12 +70,12 @@ def _nogo_stdlib_impl(ctx): ctx.actions.run( inputs = [config_file] + go_ctx.stdlib_srcs, outputs = [facts, findings], - tools = depset(go_ctx.runfiles.to_list() + ctx.files._dump_tool), + tools = depset(go_ctx.runfiles.to_list() + ctx.files._objdump_tool), executable = ctx.files._nogo[0], mnemonic = "GoStandardLibraryAnalysis", progress_message = "Analyzing Go Standard Library", arguments = go_ctx.nogo_args + [ - "-dump_tool=%s" % ctx.files._dump_tool[0].path, + "-objdump_tool=%s" % ctx.files._objdump_tool[0].path, "-stdlib=%s" % config_file.path, "-findings=%s" % findings.path, "-facts=%s" % facts.path, @@ -97,8 +95,8 @@ nogo_stdlib = go_rule( "_nogo": attr.label( default = "//tools/nogo/check:check", ), - "_dump_tool": attr.label( - default = "//tools/nogo:dump_tool", + "_objdump_tool": attr.label( + default = "//tools/nogo:objdump_tool", ), }, ) @@ -121,6 +119,8 @@ NogoInfo = provider( ) def _nogo_aspect_impl(target, ctx): + go_ctx = go_context(ctx) + # If this is a nogo rule itself (and not the shadow of a go_library or # go_binary rule created by such a rule), then we simply return nothing. # All work is done in the shadow properties for go rules. For a proto @@ -135,9 +135,6 @@ def _nogo_aspect_impl(target, ctx): else: return [NogoInfo()] - # Extract the Go context. - go_ctx = go_context(ctx) - # If we're using the "library" attribute, then we need to aggregate the # original library sources and dependencies into this target to perform # proper type analysis. @@ -227,13 +224,13 @@ def _nogo_aspect_impl(target, ctx): ctx.actions.run( inputs = inputs, outputs = [facts, findings, escapes], - tools = depset(go_ctx.runfiles.to_list() + ctx.files._dump_tool), + tools = depset(go_ctx.runfiles.to_list() + ctx.files._objdump_tool), executable = ctx.files._nogo[0], mnemonic = "GoStaticAnalysis", progress_message = "Analyzing %s" % target.label, arguments = go_ctx.nogo_args + [ "-binary=%s" % target_objfile.path, - "-dump_tool=%s" % ctx.files._dump_tool[0].path, + "-objdump_tool=%s" % ctx.files._objdump_tool[0].path, "-package=%s" % config_file.path, "-findings=%s" % findings.path, "-facts=%s" % facts.path, @@ -271,58 +268,48 @@ nogo_aspect = go_rule( attrs = { "_nogo": attr.label(default = "//tools/nogo/check:check"), "_nogo_stdlib": attr.label(default = "//tools/nogo:stdlib"), - "_dump_tool": attr.label(default = "//tools/nogo:dump_tool"), + "_objdump_tool": attr.label(default = "//tools/nogo:objdump_tool"), }, ) def _nogo_test_impl(ctx): """Check nogo findings.""" - # Build a runner that checks for the existence of the facts file. Note that - # the actual build will fail in the case of a broken analysis. We things - # this way so that any test applied is effectively pushed down to all - # upstream dependencies through the aspect. - inputs = [] - findings = [] - runner = ctx.actions.declare_file("%s-executer" % ctx.label.name) - runner_content = ["#!/bin/bash"] - for dep in ctx.attr.deps: - # Extract the findings. - info = dep[NogoInfo] - inputs.append(info.findings) - findings.append(info.findings) - - # Include all source files, transitively. This will make this target - # "directly affected" for the purpose of build analysis. - inputs += info.srcs - - # If there are findings, dump them and fail. - runner_content.append("if [[ -s \"%s\" ]]; then cat \"%s\" && exit 1; fi" % ( - info.findings.short_path, - info.findings.short_path, - )) - - # Otherwise, draw a sweet unicode checkmark with the package name (in green). - runner_content.append("echo -e \"\\033[0;32m\\xE2\\x9C\\x94\\033[0;31m\\033[0m %s\"" % info.importpath) - runner_content.append("exit 0\n") - ctx.actions.write(runner, "\n".join(runner_content), is_executable = True) + # Build a runner that checks the facts files. + findings = [dep[NogoInfo].findings for dep in ctx.attr.deps] + runner = ctx.actions.declare_file(ctx.label.name) + ctx.actions.run( + inputs = findings + ctx.files.srcs, + outputs = [runner], + tools = depset(ctx.files._gentest), + executable = ctx.files._gentest[0], + mnemonic = "Gentest", + progress_message = "Generating %s" % ctx.label, + arguments = [runner.path] + [f.path for f in findings], + ) return [DefaultInfo( - runfiles = ctx.runfiles(files = inputs), executable = runner, )] _nogo_test = rule( implementation = _nogo_test_impl, attrs = { + # deps should have only a single element. "deps": attr.label_list(aspects = [nogo_aspect]), + # srcs exist here only to ensure that this target is + # directly affected by changes to the source files. + "srcs": attr.label_list(allow_files = True), + "_gentest": attr.label(default = "//tools/nogo:gentest"), }, test = True, ) -def nogo_test(name, **kwargs): +def nogo_test(name, srcs, library, **kwargs): tags = kwargs.pop("tags", []) + ["nogo"] _nogo_test( name = name, + srcs = srcs, + deps = [library], tags = tags, **kwargs ) diff --git a/tools/nogo/gentest.sh b/tools/nogo/gentest.sh new file mode 100755 index 000000000..033da11ad --- /dev/null +++ b/tools/nogo/gentest.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Copyright 2019 The gVisor 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 -euo pipefail + +if [[ "$#" -lt 2 ]]; then + echo "usage: $0 <output> <findings...>" + exit 2 +fi +declare violations=0 +declare output=$1 +shift + +# Start the script. +echo "#!/bin/sh" > "${output}" + +# Read a list of findings files. +declare filename +declare line +for filename in "$@"; do + if [[ -z "${filename}" ]]; then + continue + fi + while read -r line; do + violations=$((${violations}+1)); + echo "echo -e '\\033[0;31m${line}\\033[0;31m\\033[0m'" >> "${output}" + done < "${filename}" +done + +# Show violations. +if [[ "${violations}" -eq 0 ]]; then + echo "echo -e '\\033[0;32mPASS\\033[0;31m\\033[0m'" >> "${output}" +else + echo "exit 1" >> "${output}" +fi diff --git a/tools/nogo/matchers.go b/tools/nogo/matchers.go index 5c39be630..b7b73fa27 100644 --- a/tools/nogo/matchers.go +++ b/tools/nogo/matchers.go @@ -102,6 +102,14 @@ func internalMatches() *pathRegexps { } } +// generatedExcluded excludes all generated code. +func generatedExcluded() *pathRegexps { + return &pathRegexps{ + expr: buildRegexps(generatedPrefix, ".*"), + include: false, + } +} + // resultExcluded excludes explicit message contents. type resultExcluded []string @@ -117,20 +125,23 @@ func (r resultExcluded) ShouldReport(d analysis.Diagnostic, _ *token.FileSet) bo // andMatcher is a composite matcher. type andMatcher struct { - first matcher - second matcher + all []matcher } // ShouldReport implements matcher.ShouldReport. func (a *andMatcher) ShouldReport(d analysis.Diagnostic, fs *token.FileSet) bool { - return a.first.ShouldReport(d, fs) && a.second.ShouldReport(d, fs) + for _, m := range a.all { + if !m.ShouldReport(d, fs) { + return false + } + } + return true } // and is a syntactic convension for andMatcher. -func and(first matcher, second matcher) *andMatcher { +func and(ms ...matcher) *andMatcher { return &andMatcher{ - first: first, - second: second, + all: ms, } } diff --git a/tools/parsers/BUILD b/tools/parsers/BUILD new file mode 100644 index 000000000..7d9c9a3fb --- /dev/null +++ b/tools/parsers/BUILD @@ -0,0 +1,27 @@ +load("//tools:defs.bzl", "go_library", "go_test") + +package(licenses = ["notice"]) + +go_test( + name = "parsers_test", + size = "small", + srcs = ["go_parser_test.go"], + library = ":parsers", + deps = [ + "//tools/bigquery", + "@com_github_google_go_cmp//cmp:go_default_library", + ], +) + +go_library( + name = "parsers", + testonly = 1, + srcs = [ + "go_parser.go", + ], + visibility = ["//:sandbox"], + deps = [ + "//test/benchmarks/tools", + "//tools/bigquery", + ], +) diff --git a/tools/parsers/go_parser.go b/tools/parsers/go_parser.go new file mode 100644 index 000000000..2cf74c883 --- /dev/null +++ b/tools/parsers/go_parser.go @@ -0,0 +1,151 @@ +// Copyright 2020 The gVisor 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 parsers holds parsers to parse Benchmark test output. +// +// Parsers parse Benchmark test output and place it in BigQuery +// structs for sending to BigQuery databases. +package parsers + +import ( + "fmt" + "strconv" + "strings" + + "gvisor.dev/gvisor/test/benchmarks/tools" + "gvisor.dev/gvisor/tools/bigquery" +) + +// parseOutput expects golang benchmark output returns a Benchmark struct formatted for BigQuery. +func parseOutput(output string, metadata *bigquery.Metadata, official bool) ([]*bigquery.Benchmark, error) { + var benchmarks []*bigquery.Benchmark + lines := strings.Split(output, "\n") + for _, line := range lines { + bm, err := parseLine(line, metadata, official) + if err != nil { + return nil, fmt.Errorf("failed to parse line '%s': %v", line, err) + } + if bm != nil { + benchmarks = append(benchmarks, bm) + } + } + return benchmarks, nil +} + +// parseLine handles parsing a benchmark line into a bigquery.Benchmark. +// +// Example: "BenchmarkRuby/server_threads.1-6 1 1397875880 ns/op 140 requests_per_second.QPS" +// +// This function will return the following benchmark: +// *bigquery.Benchmark{ +// Name: BenchmarkRuby +// []*bigquery.Condition{ +// {Name: GOMAXPROCS, 6} +// {Name: server_threads, 1} +// } +// []*bigquery.Metric{ +// {Name: ns/op, Unit: ns/op, Sample: 1397875880} +// {Name: requests_per_second, Unit: QPS, Sample: 140 } +// } +// Metadata: metadata +//} +func parseLine(line string, metadata *bigquery.Metadata, official bool) (*bigquery.Benchmark, error) { + fields := strings.Fields(line) + + // Check if this line is a Benchmark line. Otherwise ignore the line. + if len(fields) < 2 || !strings.HasPrefix(fields[0], "Benchmark") { + return nil, nil + } + + iters, err := strconv.Atoi(fields[1]) + if err != nil { + return nil, fmt.Errorf("expecting number of runs, got %s: %v", fields[1], err) + } + + name, params, err := parseNameParams(fields[0]) + if err != nil { + return nil, fmt.Errorf("parse name/params: %v", err) + } + + bm := bigquery.NewBenchmark(name, iters, official) + bm.Metadata = metadata + for _, p := range params { + bm.AddCondition(p.Name, p.Value) + } + + for i := 1; i < len(fields)/2; i++ { + value := fields[2*i] + metric := fields[2*i+1] + if err := makeMetric(bm, value, metric); err != nil { + return nil, fmt.Errorf("makeMetric on metric %q value: %s: %v", metric, value, err) + } + } + return bm, nil +} + +// parseNameParams parses the Name, GOMAXPROCS, and Params from the test. +// Field here should be of the format TESTNAME/PARAMS-GOMAXPROCS. +// Parameters will be separated by a "/" with individual params being +// "name.value". +func parseNameParams(field string) (string, []*tools.Parameter, error) { + var params []*tools.Parameter + // Remove GOMAXPROCS from end. + maxIndex := strings.LastIndex(field, "-") + if maxIndex < 0 { + return "", nil, fmt.Errorf("GOMAXPROCS not found: %s", field) + } + maxProcs := field[maxIndex+1:] + params = append(params, &tools.Parameter{ + Name: "GOMAXPROCS", + Value: maxProcs, + }) + + remainder := field[0:maxIndex] + index := strings.Index(remainder, "/") + if index == -1 { + return remainder, params, nil + } + + name := remainder[0:index] + p := remainder[index+1:] + + ps, err := tools.NameToParameters(p) + if err != nil { + return "", nil, fmt.Errorf("NameToParameters %s: %v", field, err) + } + params = append(params, ps...) + return name, params, nil +} + +// makeMetric parses metrics and adds them to the passed Benchmark. +func makeMetric(bm *bigquery.Benchmark, value, metric string) error { + switch metric { + // Ignore most output from golang benchmarks. + case "MB/s", "B/op", "allocs/op": + return nil + case "ns/op": + val, err := strconv.ParseFloat(value, 64) + if err != nil { + return fmt.Errorf("ParseFloat %s: %v", value, err) + } + bm.AddMetric(metric /*metric name*/, metric /*unit*/, val /*sample*/) + default: + m, err := tools.ParseCustomMetric(value, metric) + if err != nil { + return fmt.Errorf("ParseCustomMetric %s: %v ", metric, err) + } + bm.AddMetric(m.Name, m.Unit, m.Sample) + } + return nil +} diff --git a/tools/parsers/go_parser_test.go b/tools/parsers/go_parser_test.go new file mode 100644 index 000000000..36996b7c8 --- /dev/null +++ b/tools/parsers/go_parser_test.go @@ -0,0 +1,171 @@ +// Copyright 2020 The gVisor 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 parsers + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "gvisor.dev/gvisor/tools/bigquery" +) + +func TestParseLine(t *testing.T) { + testCases := []struct { + name string + data string + want *bigquery.Benchmark + }{ + { + name: "Iperf", + data: "BenchmarkIperf/Upload-6 1 11094914892 ns/op 4751711232 bandwidth.bytes_per_second", + want: &bigquery.Benchmark{ + Name: "BenchmarkIperf", + Condition: []*bigquery.Condition{ + { + Name: "GOMAXPROCS", + Value: "6", + }, + { + Name: "Upload", + Value: "Upload", + }, + }, + Metric: []*bigquery.Metric{ + { + Name: "ns/op", + Unit: "ns/op", + Sample: 11094914892.0, + }, + { + Name: "bandwidth", + Unit: "bytes_per_second", + Sample: 4751711232.0, + }, + }, + }, + }, + { + name: "Ruby", + data: "BenchmarkRuby/server_threads.1-6 1 1397875880 ns/op 0.00710 average_latency.s 140 requests_per_second.QPS", + want: &bigquery.Benchmark{ + Name: "BenchmarkRuby", + Condition: []*bigquery.Condition{ + { + Name: "GOMAXPROCS", + Value: "6", + }, + { + Name: "server_threads", + Value: "1", + }, + }, + Metric: []*bigquery.Metric{ + { + Name: "ns/op", + Unit: "ns/op", + Sample: 1397875880.0, + }, + { + Name: "average_latency", + Unit: "s", + Sample: 0.00710, + }, + { + Name: "requests_per_second", + Unit: "QPS", + Sample: 140.0, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseLine(tc.data, nil, false) + if err != nil { + t.Fatalf("parseLine failed with: %v", err) + } + + tc.want.Timestamp = got.Timestamp + + if !cmp.Equal(tc.want, got, nil) { + for _, c := range got.Condition { + t.Logf("Cond: %+v", c) + } + for _, m := range got.Metric { + t.Logf("Metric: %+v", m) + } + t.Fatalf("Compare failed want: %+v got: %+v", tc.want, got) + } + }) + + } +} + +func TestParseOutput(t *testing.T) { + testCases := []struct { + name string + data string + numBenchmarks int + numMetrics int + numConditions int + }{ + { + name: "Startup", + data: ` + BenchmarkStartupEmpty + BenchmarkStartupEmpty-6 2 766377884 ns/op 1 allocs/op + BenchmarkStartupNode + BenchmarkStartupNode-6 1 1752158409 ns/op 1 allocs/op + `, + numBenchmarks: 2, + numMetrics: 1, + numConditions: 1, + }, + { + name: "Ruby", + data: `BenchmarkRuby +BenchmarkRuby/server_threads.1 +BenchmarkRuby/server_threads.1-6 1 1397875880 ns/op 0.00710 average_latency.s 140 requests_per_second.QPS +BenchmarkRuby/server_threads.5 +BenchmarkRuby/server_threads.5-6 1 1416003331 ns/op 0.00950 average_latency.s 465 requests_per_second.QPS`, + numBenchmarks: 2, + numMetrics: 3, + numConditions: 2, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bms, err := parseOutput(tc.data, nil, false) + if err != nil { + t.Fatalf("parseOutput failed: %v", err) + } else if len(bms) != tc.numBenchmarks { + t.Fatalf("NumBenchmarks failed want: %d got: %d %+v", tc.numBenchmarks, len(bms), bms) + } + + for _, bm := range bms { + if len(bm.Metric) != tc.numMetrics { + t.Fatalf("NumMetrics failed want: %d got: %d %+v", tc.numMetrics, len(bm.Metric), bm.Metric) + } + + if len(bm.Condition) != tc.numConditions { + t.Fatalf("NumConditions failed want: %d got: %d %+v", tc.numConditions, len(bm.Condition), bm.Condition) + } + } + }) + } +} |