Repository: googleapis/api-linter
Pull request #194 (2019-09-27 18:14:32)

[feat] Add parsing from proto string.
This PR adds a `ParseProtoString` function in an internal testutils
directory, to allow easier to read tests, and also permit tests
that are location-sensitive.

This is a hard prerequisite for the syntax and package location work
(which I have more or less done but split into a different PR).


Patch:
diff --git a/go.mod b/go.mod
index fc8bb8d..d55edd6 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/golang/protobuf v1.3.2
 	github.com/google/go-cmp v0.3.1
 	github.com/jhump/protoreflect v1.5.0
+	github.com/lithammer/dedent v1.1.0
 	github.com/stoewer/go-strcase v1.0.2
 	github.com/urfave/cli v1.22.1
 	google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c
diff --git a/go.sum b/go.sum
index 3b24757..2147bc0 100644
--- a/go.sum
+++ b/go.sum
@@ -19,6 +19,8 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/jhump/protoreflect v1.5.0 h1:NgpVT+dX71c8hZnxHof2M7QDK7QtohIJ7DYycjnkyfc=
 github.com/jhump/protoreflect v1.5.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
+github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
+github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=


Test patch:
diff --git a/internal/testutils/parse.go b/internal/testutils/parse.go
new file mode 100644
index 0000000..c275780
--- /dev/null
+++ b/internal/testutils/parse.go
@@ -0,0 +1,43 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+// 		https://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 (
+	"strings"
+	"testing"
+
+	"github.com/jhump/protoreflect/desc"
+	"github.com/jhump/protoreflect/desc/protoparse"
+	"github.com/lithammer/dedent"
+)
+
+// ParseProtoString parses a string representing a proto file, and returns
+// a FileDescriptor.
+//
+// It dedents the string before parsing.
+// It is unable to handle imports, and panics if there is any error.
+func ParseProtoString(t *testing.T, src string) *desc.FileDescriptor {
+	parser := protoparse.Parser{
+		Accessor: protoparse.FileContentsFromMap(map[string]string{
+			"test.proto": strings.TrimSpace(dedent.Dedent(src)),
+		}),
+		IncludeSourceCodeInfo: true,
+	}
+	fds, err := parser.ParseFiles("test.proto")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	return fds[0]
+}
diff --git a/internal/testutils/parse_test.go b/internal/testutils/parse_test.go
new file mode 100644
index 0000000..138239b
--- /dev/null
+++ b/internal/testutils/parse_test.go
@@ -0,0 +1,80 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+// 		https://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 (
+	"sync"
+	"testing"
+
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestParseProtoString(t *testing.T) {
+	fd := ParseProtoString(t, `
+		syntax = "proto3";
+
+		message Foo {
+			int32 bar = 1;
+			int64 baz = 2;
+		}
+
+		message Spam {
+			string eggs = 2;
+		}
+	`)
+	if !fd.IsProto3() {
+		t.Errorf("Expected a proto3 file descriptor.")
+	}
+	tests := []struct {
+		name       string
+		descriptor desc.Descriptor
+	}{
+		{"Foo", fd.GetMessageTypes()[0]},
+		{"bar", fd.GetMessageTypes()[0].GetFields()[0]},
+		{"baz", fd.GetMessageTypes()[0].GetFields()[1]},
+		{"Spam", fd.GetMessageTypes()[1]},
+		{"eggs", fd.GetMessageTypes()[1].GetFields()[0]},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if got, want := test.descriptor.GetName(), test.name; got != want {
+				t.Errorf("Got %q, expected %q.", got, want)
+			}
+		})
+	}
+}
+
+func TestParseProtoStringError(t *testing.T) {
+	canary := &testing.T{}
+
+	// t.Fatalf will exit the goroutine, so to test this,
+	// we run the test in a different goroutine.
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		ParseProtoString(canary, `
+			syntax = "proto3";
+			message Foo {}
+			The quick brown fox jumped over the lazy dogs.
+		`)
+	}()
+	wg.Wait()
+
+	// Verify that the testing.T object was given a failure.
+	if !canary.Failed() {
+		t.Errorf("Expected syntax error to cause a fatal error.")
+	}
+}