github.com/cuberat/protoc-gen-docjson@v1.1.1-0.20230510142946-907a0bd92b78/tests/proto1_test.go (about)

     1  package proto1_test
     2  
     3  import (
     4  	// Built-in/core modules.
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	exec "os/exec"
    10  	"path"
    11  	"reflect"
    12  	"sort"
    13  	"testing"
    14  	// Generated code.
    15  	// First-party modules.
    16  )
    17  
    18  func TestComments(t *testing.T) {
    19  	data, ok := do_setup(t)
    20  	if !ok {
    21  		return
    22  	}
    23  
    24  	if !t.Run("len check",
    25  		func(st *testing.T) {
    26  			do_len_checks(st, data)
    27  		},
    28  	) {
    29  		t.Error("Len checks failed. Bailing out of tests.")
    30  		return
    31  	}
    32  
    33  	t.Run("syntax data check",
    34  		func(st *testing.T) {
    35  			do_check_syntax(st, data)
    36  		},
    37  	)
    38  
    39  	t.Run("svc check",
    40  		func(st *testing.T) {
    41  			do_check_services(st, data)
    42  		},
    43  	)
    44  
    45  	t.Run("file check",
    46  		func(st *testing.T) {
    47  			do_check_files(st, data)
    48  		},
    49  	)
    50  
    51  	t.Run("msg check",
    52  		func(st *testing.T) {
    53  			do_check_messages(st, data)
    54  		},
    55  	)
    56  
    57  	t.Run("enum check",
    58  		func(st *testing.T) {
    59  			do_check_enums(st, data)
    60  		},
    61  	)
    62  
    63  	t.Run("service_deps check",
    64  		func(st *testing.T) {
    65  			do_check_service_deps(st, data)
    66  		},
    67  	)
    68  
    69  	t.Run("service_file_dep check",
    70  		func(st *testing.T) {
    71  			do_check_service_file_deps(st, data)
    72  		},
    73  	)
    74  }
    75  
    76  func do_check_service_file_deps(t *testing.T, data map[string]any) {
    77  	svc_file_deps, ok := data["service_file_deps"].(map[string]any)
    78  	if !ok {
    79  		t.Errorf("wrong type for service_file_deps: got %T, "+
    80  			"expected map[string]any", data["service_file_deps"])
    81  		return
    82  	}
    83  
    84  	if len(svc_file_deps) != 1 {
    85  		t.Errorf("wrong number of items in service_file_deps: got %d, "+
    86  			"expected 1", len(svc_file_deps))
    87  		return
    88  	}
    89  
    90  	svc_name := "MyServices.Service.Tester"
    91  	expected_deps := []any{"tester.proto"}
    92  	deps, ok := svc_file_deps[svc_name].([]any)
    93  	if !ok {
    94  		t.Errorf("wrong type for svc dep list %q: got %T, expected []any",
    95  			svc_name, svc_file_deps[svc_name])
    96  		return
    97  	}
    98  	if !reflect.DeepEqual(deps, expected_deps) {
    99  		t.Errorf("wrong list of dependencies for svc %q: got %v, expected %v",
   100  			svc_name, deps, expected_deps)
   101  		return
   102  	}
   103  }
   104  
   105  func do_check_service_deps(t *testing.T, data map[string]any) {
   106  	svc_deps, ok := data["service_deps"].(map[string]any)
   107  	if !ok {
   108  		t.Errorf("wrong type for service_deps: got %T, "+
   109  			"expected map[string]any", data["service_deps"])
   110  		return
   111  	}
   112  
   113  	if len(svc_deps) != 1 {
   114  		t.Errorf("wrong number of items in service_deps: got %d, "+
   115  			"expected 1", len(svc_deps))
   116  		return
   117  	}
   118  
   119  	svc_name := "MyServices.Service.Tester"
   120  	this_svc_deps, ok := svc_deps[svc_name].([]any)
   121  	if !ok {
   122  		t.Errorf("wrong type for dep %q dep list: got %T, "+
   123  			"expected []any", svc_name, svc_deps[svc_name])
   124  		return
   125  	}
   126  
   127  	expected_deps := []any{
   128  		"MyServices.Tester.TesterRequest",
   129  		"MyServices.Tester.ClientInfo",
   130  		"MyServices.Tester.TesterResponse",
   131  		"MyServices.Tester.TesterResponse.ResponseThing",
   132  		"MyServices.Tester.TesterResponse.EmbeddedTester",
   133  	}
   134  	if !reflect.DeepEqual(this_svc_deps, expected_deps) {
   135  		t.Errorf("wrong service dep list for %q: got %v, expected %v",
   136  			svc_name, this_svc_deps, expected_deps)
   137  		return
   138  	}
   139  }
   140  
   141  func do_check_services(t *testing.T, data map[string]any) {
   142  	svc_map, ok := data["service_map"].(map[string]any)
   143  	if !ok {
   144  		t.Errorf("Wrong type for service_map: %T", data["service_map"])
   145  		return
   146  	}
   147  
   148  	svc_name := "MyServices.Service.Tester"
   149  	svc, ok := svc_map[svc_name].(map[string]any)
   150  	if !ok {
   151  		t.Errorf("Wrong type for service: %T", svc_map["service_name"])
   152  		return
   153  	}
   154  
   155  	label := fmt.Sprintf("service %s", svc["name"].(string))
   156  
   157  	exp_desc := "Tester service. Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Suspendisse a cursus mauris. Proin porta mi nisl, vel iaculis leo mattis\n ut. Maecenas lacus urna, dapibus sit amet leo id, rutrum fermentum justo.\n Cras porta, nulla vel euismod maximus, lacus magna ultrices metus, sit amet\n eleifend lacus libero et lacus. Cras a facilisis est. Praesent augue nisl,\n tincidunt vel ex mattis, efficitur fermentum sem. Ut congue tellus ut\n accumsan condimentum. Sed quis leo nec turpis maximus molestie quis sit\n amet erat."
   158  
   159  	desc := svc["description"].(string)
   160  	if desc != exp_desc {
   161  		t.Errorf("incorrect description for service %s. Got %q, expected %q",
   162  			svc_name, desc, exp_desc)
   163  		return
   164  	}
   165  
   166  	leading_comments := svc["leading_comments"].(string)
   167  	if leading_comments != exp_desc {
   168  		t.Errorf("incorrect leading_comments for service %s. "+
   169  			"Got %q, expected %q", svc_name, leading_comments, exp_desc)
   170  		return
   171  	}
   172  
   173  	leading_detached_comments := svc["leading_detached_comments"].([]any)
   174  	if len(leading_detached_comments) != 0 {
   175  		t.Errorf("got %d leading_detached_comments when 0 were expected",
   176  			len(leading_detached_comments))
   177  		return
   178  	}
   179  
   180  	check_methods(t, svc)
   181  
   182  	test_spec := map[string]any{
   183  		"options": map[string]any{
   184  			"deprecated": false,
   185  		},
   186  		"custom_options": map[string]any{
   187  			"service_not_implemented": true,
   188  		},
   189  	}
   190  
   191  	t.Run("options",
   192  		func(st *testing.T) {
   193  			do_check_options(st, svc, test_spec, label)
   194  		},
   195  	)
   196  }
   197  
   198  func check_one_method(
   199  	t *testing.T,
   200  	this_method map[string]any,
   201  	test_spec map[string]any,
   202  	label string,
   203  ) {
   204  	method_name := this_method["name"].(string)
   205  	method_full_name := this_method["full_name"].(string)
   206  
   207  	exp_name := test_spec["name"].(string)
   208  	if method_name != exp_name {
   209  		t.Errorf("got method name %q, expected %q", method_name, exp_name)
   210  	}
   211  	exp_full_name := test_spec["full_name"]
   212  	if method_full_name != exp_full_name {
   213  		t.Errorf("got method full name %q, expected %q", method_full_name,
   214  			exp_full_name)
   215  	}
   216  
   217  	desc := this_method["description"].(string)
   218  	exp_desc := test_spec["desc"]
   219  
   220  	if desc != exp_desc {
   221  		t.Errorf("incorrect description for service method %s: got %q, "+
   222  			"expected %q", method_name, desc, exp_desc)
   223  	}
   224  
   225  	t.Run("request_response",
   226  		func(st *testing.T) {
   227  			check_req_resp(st, this_method, test_spec, label)
   228  		},
   229  	)
   230  
   231  	t.Run("options",
   232  		func(st *testing.T) {
   233  			do_check_options(st, this_method, test_spec, label)
   234  		},
   235  	)
   236  
   237  	defined_in := test_spec["defined_in"].(string)
   238  	if file_name := this_method["defined_in"].(string); file_name != defined_in {
   239  		t.Errorf("incorrect file name in `defined_in` field: got %q, "+
   240  			"expected %q", this_method["defined_in"].(string), defined_in)
   241  	}
   242  
   243  }
   244  
   245  func check_req_resp(
   246  	t *testing.T,
   247  	this_method map[string]any,
   248  	test_spec map[string]any,
   249  	label string,
   250  ) {
   251  	exp_request_type := test_spec["request_type"].(string)
   252  	got_request_type := this_method["request_type"].(string)
   253  	if got_request_type != exp_request_type {
   254  		t.Errorf("incorrect request type for %s: got %q, expected %q", label,
   255  			got_request_type, exp_request_type)
   256  	}
   257  	exp_request_type = test_spec["request_full_type"].(string)
   258  	got_request_type = this_method["request_full_type"].(string)
   259  	if got_request_type != exp_request_type {
   260  		t.Errorf("incorrect request full type for %s: got %q, expected %q",
   261  			label, got_request_type, exp_request_type)
   262  	}
   263  
   264  	exp_response_type := test_spec["response_type"].(string)
   265  	got_response_type := this_method["response_type"].(string)
   266  	if got_response_type != exp_response_type {
   267  		t.Errorf("incorrect response type for %s: got %q, expected %q",
   268  			label, got_response_type, exp_response_type)
   269  	}
   270  
   271  	exp_response_type = test_spec["response_full_type"].(string)
   272  	got_response_type = this_method["response_full_type"].(string)
   273  	if got_response_type != exp_response_type {
   274  		t.Errorf("incorrect response full type for %s: got %q, expected %q",
   275  			label, got_response_type, exp_response_type)
   276  	}
   277  }
   278  
   279  // Performs unit tests for service methods. Returns true if all tests pass.
   280  // Returns false, otherwise.
   281  func check_methods(t *testing.T, svc map[string]any) {
   282  	methods := svc["methods"].([]any)
   283  	if len(methods) != 2 {
   284  		t.Fatalf("got %d methods, expected 2", len(methods))
   285  	}
   286  
   287  	method_test_spec := []map[string]any{
   288  		{
   289  			"name":       "RunTestV2",
   290  			"full_name":  "MyServices.Service.Tester.RunTestV2",
   291  			"desc":       "Leading comment for the RunTestV2 method which is marked not_implemented\n via a custom option method_not_implemented.",
   292  			"defined_in": "service-tester.proto",
   293  			"options": map[string]any{
   294  				"deprecated": false,
   295  			},
   296  			"custom_options": map[string]any{
   297  				"method_not_implemented": true,
   298  			},
   299  			"request_type":       "TesterRequest",
   300  			"request_full_type":  "MyServices.Tester.TesterRequest",
   301  			"response_type":      "TesterResponse",
   302  			"response_full_type": "MyServices.Tester.TesterResponse",
   303  		},
   304  
   305  		{
   306  			"name":       "RunTest",
   307  			"full_name":  "MyServices.Service.Tester.RunTest",
   308  			"desc":       "Leading comment for the RunTest method is which marked deprecated.",
   309  			"defined_in": "service-tester.proto",
   310  			"options": map[string]any{
   311  				"deprecated": true,
   312  			},
   313  			"custom_options":     map[string]any{},
   314  			"request_type":       "TesterRequest",
   315  			"request_full_type":  "MyServices.Tester.TesterRequest",
   316  			"response_type":      "TesterResponse",
   317  			"response_full_type": "MyServices.Tester.TesterResponse",
   318  		},
   319  	}
   320  
   321  	for i, this_method_any := range methods {
   322  		this_method := this_method_any.(map[string]any)
   323  		this_test_spec := method_test_spec[i]
   324  		this_method_label := fmt.Sprintf("method %s",
   325  			this_method["name"].(string))
   326  
   327  		t.Run(this_method_label, func(st *testing.T) {
   328  			check_one_method(st, this_method, this_test_spec, this_method_label)
   329  		})
   330  	}
   331  }
   332  
   333  func do_check_options(
   334  	t *testing.T,
   335  	data map[string]any,
   336  	test_spec map[string]any,
   337  	label string,
   338  ) {
   339  	// Check standard options.
   340  	std_options := data["options"].(map[string]any)
   341  	std_options_test := test_spec["options"].(map[string]any)
   342  	for _, opt_name := range get_sorted_keys(std_options_test) {
   343  		opt_val_exp := std_options_test[opt_name]
   344  		opt_val_got := std_options[opt_name]
   345  		if !reflect.DeepEqual(opt_val_exp, opt_val_got) {
   346  			t.Logf("incorrect value for standard option %q in %s: "+
   347  				"got %v, expected %v", label, opt_name, opt_val_got,
   348  				opt_val_exp)
   349  		}
   350  	}
   351  
   352  	// Check custom options.
   353  	cust_options := data["custom_options"].(map[string]any)
   354  	cust_options_test := test_spec["custom_options"].(map[string]any)
   355  	for _, opt_name := range get_sorted_keys(cust_options_test) {
   356  		opt_val_exp := cust_options_test[opt_name]
   357  		opt_val_got := cust_options[opt_name]
   358  		if !reflect.DeepEqual(opt_val_exp, opt_val_got) {
   359  			t.Logf("incorrect value for custom option %q in %s: "+
   360  				"got %v, expected %v", label, opt_name, opt_val_got,
   361  				opt_val_exp)
   362  		}
   363  	}
   364  }
   365  
   366  func check_one_file(
   367  	t *testing.T,
   368  	this_file, test_spec map[string]any,
   369  	label string,
   370  ) {
   371  	check_extensions(t, this_file, test_spec, label)
   372  	do_check_options(t, this_file, test_spec, label)
   373  	field_check_list := []string{
   374  		"version", "name", "package", "defined_in",
   375  	}
   376  	check_fields_equal(t, this_file, test_spec, label, field_check_list)
   377  }
   378  
   379  func do_check_files(t *testing.T, data map[string]any) {
   380  	file_test_spec := map[string]any{
   381  		"service-tester.proto": map[string]any{
   382  			"extensions": []map[string]any{},
   383  			"name":       "service-tester.proto",
   384  			"package":    "MyServices.Service",
   385  			"messages":   []map[string]any{},
   386  			"enums":      []map[string]any{},
   387  			"options": map[string]any{
   388  				"java_package":           "myservices.service",
   389  				"java_outer_classname":   "",
   390  				"java_multiple_files":    false,
   391  				"java_string_check_utf8": false,
   392  				"go_package":             "myservices/grpc/service",
   393  				"deprecated":             false,
   394  				"cc_enable_arenas":       true,
   395  				"objc_class_prefix":      "",
   396  				"csharp_namespace":       "",
   397  				"swift_prefix":           "",
   398  				"php_class_prefix":       "",
   399  				"php_namespace":          "",
   400  				"php_metadata_namespace": "",
   401  				"ruby_package":           "",
   402  			},
   403  			"custom_options": map[string]any{},
   404  		},
   405  		"tester.proto": map[string]any{
   406  			"description":               "Tester main structure, TesterRequest.",
   407  			"leading_comments":          "Tester main structure, TesterRequest.",
   408  			"trailing_comments":         "",
   409  			"leading_detached_comments": map[string]any{},
   410  			"name":                      "tester.proto",
   411  			"package":                   "MyServices.Tester",
   412  			"options": map[string]any{
   413  				"java_package":           "Myservices.tester",
   414  				"java_outer_classname":   "",
   415  				"java_multiple_files":    false,
   416  				"java_string_check_utf8": false,
   417  				"go_package":             "Myservices/grpc/tester",
   418  				"deprecated":             false,
   419  				"cc_enable_arenas":       true,
   420  				"objc_class_prefix":      "",
   421  				"csharp_namespace":       "",
   422  				"swift_prefix":           "",
   423  				"php_class_prefix":       "",
   424  				"php_namespace":          "",
   425  				"php_metadata_namespace": "",
   426  				"ruby_package":           "",
   427  			},
   428  			"custom_options": map[string]any{
   429  				"file_deprecated": true,
   430  				"file_double":     float64(5643343.423),
   431  				"file_float":      float64(0.569),
   432  				"file_int64":      float64(-343434343),
   433  				"file_mnemonic":   "some random name",
   434  			},
   435  			"extensions": []map[string]any{
   436  				{
   437  					"description":               "Leading comment for custom option field_required.",
   438  					"leading_comments":          "Leading comment for custom option field_required.",
   439  					"trailing_comments":         "",
   440  					"leading_detached_comments": []any{},
   441  					"name":                      "field_required",
   442  					"full_name":                 "MyServices.Tester.field_required",
   443  					"field_number":              float64(51234),
   444  					"type":                      "bool",
   445  					"extendee":                  ".google.protobuf.FieldOptions",
   446  					"defined_in":                "tester.proto",
   447  				},
   448  				{
   449  					"description":               "Trailing for method_not_implemented.",
   450  					"leading_comments":          "",
   451  					"trailing_comments":         "Trailing for method_not_implemented.",
   452  					"leading_detached_comments": []any{},
   453  					"name":                      "method_not_implemented",
   454  					"full_name":                 "MyServices.Tester.method_not_implemented",
   455  					"field_number":              float64(51235),
   456  					"type":                      "bool",
   457  					"extendee":                  ".google.protobuf.MethodOptions",
   458  					"defined_in":                "tester.proto",
   459  				},
   460  				{
   461  					"description":               "",
   462  					"leading_comments":          "",
   463  					"trailing_comments":         "",
   464  					"leading_detached_comments": []any{},
   465  					"name":                      "message_not_implemented",
   466  					"full_name":                 "MyServices.Tester.message_not_implemented",
   467  					"field_number":              float64(51236),
   468  					"type":                      "bool",
   469  					"extendee":                  ".google.protobuf.MessageOptions",
   470  					"defined_in":                "tester.proto",
   471  				},
   472  				{
   473  					"description":               "",
   474  					"leading_comments":          "",
   475  					"trailing_comments":         "",
   476  					"leading_detached_comments": []any{},
   477  					"name":                      "service_not_implemented",
   478  					"full_name":                 "MyServices.Tester.service_not_implemented",
   479  					"field_number":              float64(51237),
   480  					"type":                      "bool",
   481  					"extendee":                  ".google.protobuf.ServiceOptions",
   482  					"defined_in":                "tester.proto",
   483  				},
   484  				{
   485  					"description":               "",
   486  					"leading_comments":          "",
   487  					"trailing_comments":         "",
   488  					"leading_detached_comments": []any{},
   489  					"name":                      "file_deprecated",
   490  					"full_name":                 "MyServices.Tester.file_deprecated",
   491  					"field_number":              float64(51238),
   492  					"type":                      "bool",
   493  					"extendee":                  ".google.protobuf.FileOptions",
   494  					"defined_in":                "tester.proto",
   495  				},
   496  				{
   497  					"description":               "",
   498  					"leading_comments":          "",
   499  					"trailing_comments":         "",
   500  					"leading_detached_comments": []any{},
   501  					"name":                      "file_mnemonic",
   502  					"full_name":                 "MyServices.Tester.file_mnemonic",
   503  					"field_number":              float64(51240),
   504  					"type":                      "string",
   505  					"extendee":                  ".google.protobuf.FileOptions",
   506  					"defined_in":                "tester.proto",
   507  				},
   508  				{
   509  					"description":               "",
   510  					"leading_comments":          "",
   511  					"trailing_comments":         "",
   512  					"leading_detached_comments": []any{},
   513  					"name":                      "file_double",
   514  					"full_name":                 "MyServices.Tester.file_double",
   515  					"field_number":              float64(51241),
   516  					"type":                      "float",
   517  					"extendee":                  ".google.protobuf.FileOptions",
   518  					"defined_in":                "tester.proto",
   519  				},
   520  				{
   521  					"description":               "",
   522  					"leading_comments":          "",
   523  					"trailing_comments":         "",
   524  					"leading_detached_comments": []any{},
   525  					"name":                      "file_float",
   526  					"full_name":                 "MyServices.Tester.file_float",
   527  					"field_number":              float64(51242),
   528  					"type":                      "float",
   529  					"extendee":                  ".google.protobuf.FileOptions",
   530  					"defined_in":                "tester.proto",
   531  				},
   532  				{
   533  					"description":               "",
   534  					"leading_comments":          "",
   535  					"trailing_comments":         "",
   536  					"leading_detached_comments": []any{},
   537  					"name":                      "file_int64",
   538  					"full_name":                 "MyServices.Tester.file_int64",
   539  					"field_number":              float64(51243),
   540  					"type":                      "float",
   541  					"extendee":                  ".google.protobuf.FileOptions",
   542  					"defined_in":                "tester.proto",
   543  				},
   544  				{
   545  					"description":               "",
   546  					"leading_comments":          "",
   547  					"trailing_comments":         "",
   548  					"leading_detached_comments": []any{},
   549  					"name":                      "enum_deprecated",
   550  					"full_name":                 "MyServices.Tester.enum_deprecated",
   551  					"field_number":              float64(51239),
   552  					"type":                      "bool",
   553  					"extendee":                  ".google.protobuf.EnumOptions",
   554  					"defined_in":                "tester.proto",
   555  				},
   556  			},
   557  		},
   558  		"subdir/docstuff.proto": map[string]any{
   559  			"extensions": []map[string]any{},
   560  			"name":       "subdir/docstuff.proto",
   561  			"package":    "MyServices.Tester.Subdir",
   562  			"options": map[string]any{
   563  				"java_package":           "",
   564  				"java_outer_classname":   "",
   565  				"java_multiple_files":    false,
   566  				"java_string_check_utf8": false,
   567  				"go_package":             "",
   568  				"deprecated":             false,
   569  				"cc_enable_arenas":       true,
   570  				"objc_class_prefix":      "",
   571  				"csharp_namespace":       "",
   572  				"swift_prefix":           "",
   573  				"php_class_prefix":       "",
   574  				"php_namespace":          "",
   575  				"php_metadata_namespace": "",
   576  				"ruby_package":           "",
   577  			},
   578  			"custom_options":          map[string]any{},
   579  			"declared_custom_options": map[string]any{},
   580  		},
   581  	}
   582  
   583  	file_map := data["file_map"].(map[string]any)
   584  	file_names := get_sorted_keys(file_map)
   585  	t.Logf("file_names: %v", file_names)
   586  	for _, file_name := range file_names {
   587  		t.Logf("file_name: %s", file_name)
   588  		file_data := file_map[file_name].(map[string]any)
   589  		label := fmt.Sprintf("file %s", file_name)
   590  		t.Run(label, func(st *testing.T) {
   591  			check_one_file(st, file_data,
   592  				file_test_spec[file_name].(map[string]any), label)
   593  		})
   594  	}
   595  }
   596  
   597  func do_check_messages(t *testing.T, data map[string]any) {
   598  	msg_map := data["message_map"].(map[string]any)
   599  	msg_full_name := "MyServices.Tester.TesterRequest"
   600  	msg := msg_map[msg_full_name].(map[string]any)
   601  	fields := msg["fields"].([]any)
   602  
   603  	exp_msg_comments := map[string]any{
   604  		"description":               "Tester main structure, TesterRequest.",
   605  		"leading_comments":          "Tester main structure, TesterRequest.",
   606  		"trailing_comments":         "",
   607  		"leading_detached_comments": []string{},
   608  	}
   609  
   610  	check_comments(t, msg, exp_msg_comments,
   611  		fmt.Sprintf("msg %s", msg_full_name))
   612  
   613  	field := fields[0].(map[string]any)
   614  
   615  	options := field["options"]
   616  	custom_options := field["custom_options"]
   617  	exp_field_comments := map[string]any{
   618  		"description":               "Leading comment for the client_info field.",
   619  		"leading_comments":          "Leading comment for the client_info field.",
   620  		"trailing_comments":         "",
   621  		"leading_detached_comments": []string{},
   622  	}
   623  
   624  	check_comments(t, field, exp_field_comments,
   625  		fmt.Sprintf("field number %d of %s",
   626  			field["field_number"], msg_full_name))
   627  
   628  	exp_options := map[string]any{
   629  		"ctype":      0,
   630  		"packed":     false,
   631  		"jstype":     0,
   632  		"lazy":       false,
   633  		"deprecated": false,
   634  	}
   635  	exp_custom_options := map[string]any{
   636  		"field_required": true,
   637  	}
   638  
   639  	if !reflect.DeepEqual(options, exp_options) {
   640  		t.Logf("options in first field of %s incorrect: "+
   641  			"got %v, expected %v", msg_full_name, options, exp_options)
   642  	}
   643  
   644  	if !reflect.DeepEqual(custom_options, exp_custom_options) {
   645  		t.Logf("custom options in first field of %s incorrect: "+
   646  			"got %v, expected %v", msg_full_name, custom_options,
   647  			exp_custom_options)
   648  	}
   649  }
   650  
   651  func check_extensions(
   652  	t *testing.T, data map[string]any,
   653  	test_spec map[string]any,
   654  	label string,
   655  ) {
   656  	if data["extensions"] == nil {
   657  		return
   658  	}
   659  	extension_list_any := data["extensions"].([]any)
   660  	ext_spec_list := test_spec["extensions"].([]map[string]any)
   661  
   662  	extension_map := make(map[string]map[string]any, len(extension_list_any))
   663  	for _, ext_any := range extension_list_any {
   664  		ext := ext_any.(map[string]any)
   665  		name := ext["full_name"].(string)
   666  		extension_map[name] = ext
   667  	}
   668  
   669  	if len(extension_map) != len(ext_spec_list) {
   670  		t.Fatalf("mismatch in number of found extensions vs expected in %s: "+
   671  			"found %d, expected %d", label, len(extension_map),
   672  			len(ext_spec_list))
   673  	}
   674  
   675  	for _, ext_spec := range ext_spec_list {
   676  		ext_name := ext_spec["full_name"].(string)
   677  		this_label := fmt.Sprintf("extension %s in %s", ext_name, label)
   678  		ext := extension_map[ext_name]
   679  		if ext == nil {
   680  			t.Fatalf("missing %s", this_label)
   681  		}
   682  
   683  		t.Run(this_label, func(st *testing.T) {
   684  			check_one_extension(st, ext, ext_spec, this_label)
   685  		})
   686  
   687  	}
   688  }
   689  
   690  func check_one_extension(
   691  	t *testing.T,
   692  	ext, ext_test_spec map[string]any,
   693  	label string,
   694  ) {
   695  	check_fields_equal(t, ext, ext_test_spec, label, nil)
   696  }
   697  
   698  func check_fields_equal(
   699  	t *testing.T,
   700  	data map[string]any,
   701  	test_spec map[string]any,
   702  	label string,
   703  	field_list []string) {
   704  
   705  	if len(field_list) == 0 {
   706  		field_list = get_sorted_keys(test_spec)
   707  	}
   708  
   709  	for _, field := range field_list {
   710  		if !reflect.DeepEqual(data[field], test_spec[field]) {
   711  			t.Errorf("discrepancy with field %s: got %v (%T), expected %v (%T)",
   712  				field, data[field], data[field], test_spec[field], test_spec[field])
   713  		}
   714  	}
   715  }
   716  
   717  func do_check_enums(t *testing.T, data map[string]any) {
   718  	expected := map[string]any{
   719  		"leading_comments":  "Leading comment for enum TesterError.",
   720  		"trailing_comments": "",
   721  		"leading_detached_comments": []string{
   722  			"Detached leading comment for enum TesterError",
   723  		},
   724  		"name":        "TesterError",
   725  		"full_name":   "MyServices.Tester.TesterError",
   726  		"description": "Leading comment for enum TesterError.",
   727  		"values": []map[string]any{
   728  			{
   729  				"description":               "Leading comment for enum value NONE.",
   730  				"leading_comments":          "Leading comment for enum value NONE.",
   731  				"trailing_comments":         "",
   732  				"leading_detached_comments": []string{},
   733  				"Name":                      "NONE",
   734  				"Number":                    0,
   735  				"options": map[string]any{
   736  					"deprecated": false,
   737  				},
   738  				"custom_options": map[string]any{},
   739  			},
   740  		},
   741  		"options": map[string]any{
   742  			"allow_alias": false,
   743  			"deprecated":  false,
   744  		},
   745  		"custom_options": map[string]any{
   746  			"enum_deprecated": true,
   747  		},
   748  		"defined_in": "tester.proto",
   749  	}
   750  
   751  	enum_map := data["enum_map"].(map[string]any)
   752  	this_enum := enum_map["MyServices.Tester.TesterError"].(map[string]any)
   753  
   754  	check_comments(t, this_enum, expected, "enum MyServices.Tester.TesterError")
   755  
   756  	enum_vals := this_enum["values"].([]any)
   757  	if len(enum_vals) != 3 {
   758  		t.Errorf("incorrect number of values for enum "+
   759  			"MyServices.Tester.TesterError: got %d, expected 3",
   760  			len(enum_vals))
   761  	}
   762  
   763  	enum_val_0 := enum_vals[0].(map[string]any)
   764  	expected_enum_vals := expected["values"].([]map[string]any)
   765  	exp_val_0 := expected_enum_vals[0]
   766  
   767  	check_comments(t, enum_val_0, exp_val_0,
   768  		"val 0 from enum MyServices.Tester.TesterError")
   769  
   770  	do_check_options(t, this_enum, expected,
   771  		"options for enum MyServices.Tester.TesterError")
   772  
   773  }
   774  
   775  func do_check_syntax(t *testing.T, data map[string]any) {
   776  	file_map, ok := data["file_map"].(map[string]any)
   777  	if !ok {
   778  		t.Errorf("Wrong type for file_map: %T", file_map)
   779  		return
   780  	}
   781  
   782  	file_name := "tester.proto"
   783  	tester_file, ok := file_map[file_name].(map[string]any)
   784  	if !ok {
   785  		t.Errorf("Wrong type for %q info: %T", file_name, file_map[file_name])
   786  		return
   787  	}
   788  
   789  	syntax_data, ok := tester_file["syntax"].(map[string]any)
   790  	if !ok {
   791  		t.Errorf("Wrong type for syntax_data: %T", tester_file["syntax"])
   792  		return
   793  	}
   794  
   795  	field_desc := fmt.Sprintf("syntax (in %q)", file_name)
   796  
   797  	test_spec_map := map[string]any{
   798  		"leading_comments":          "This is the syntax statement leading comment.",
   799  		"trailing_comments":         "This is a trailing comment for syntax.",
   800  		"description":               "This is the syntax statement leading comment. This is a trailing comment for syntax.",
   801  		"leading_detached_comments": []string{"Leading detached comment for the syntax statement.\n A second line."},
   802  	}
   803  
   804  	check_comments(t, syntax_data, test_spec_map, field_desc)
   805  }
   806  
   807  func check_comments(
   808  	t *testing.T,
   809  	data, test_spec map[string]any,
   810  	label string,
   811  ) {
   812  	check_comment_field(t, data, label, "leading_comments",
   813  		test_spec)
   814  	check_comment_field(t, data, label, "trailing_comments",
   815  		test_spec)
   816  	check_comment_field(t, data, label, "description",
   817  		test_spec)
   818  	check_comment_list(t, data, label, "leading_detached_comments",
   819  		test_spec)
   820  }
   821  
   822  func check_comment_list(
   823  	t *testing.T, data map[string]any,
   824  	field_desc, field_name string,
   825  	test_spec map[string]any,
   826  ) {
   827  	comment_list, ok := data[field_name].([]any)
   828  	if !ok {
   829  		t.Errorf("Wrong type for %s %s field: %T", field_desc, field_name,
   830  			data[field_name])
   831  	}
   832  	expected := test_spec[field_name].([]string)
   833  	if len(expected) != len(comment_list) {
   834  		t.Errorf("Wrong length for %s %s list: got %d, expected %d",
   835  			field_desc, field_name, len(comment_list), len(expected))
   836  		return
   837  	}
   838  
   839  	for i, exp_val := range expected {
   840  		comment, ok := comment_list[i].(string)
   841  		if !ok {
   842  			t.Errorf("Wrong type for comment list index %d for %s %s: %T",
   843  				i, field_desc, field_name, comment_list[i])
   844  			return
   845  		}
   846  
   847  		if comment != exp_val {
   848  			t.Errorf("Incorrect comment at index %d for %s %s: got %q, "+
   849  				"expected %q", i, field_desc, field_name, comment, exp_val)
   850  		}
   851  	}
   852  }
   853  
   854  func check_comment_field(
   855  	t *testing.T,
   856  	data map[string]any,
   857  	label, field_name string,
   858  	test_spec map[string]any,
   859  ) {
   860  	comment, ok := data[field_name].(string)
   861  	if !ok {
   862  		t.Errorf("Wrong type for %s %s field: %T", label,
   863  			field_name, data[field_name])
   864  		return
   865  	}
   866  
   867  	expected := test_spec[field_name].(string)
   868  	if comment != expected {
   869  		t.Errorf("comment did not match for %s %s field. Got %q, expected %q.",
   870  			label, field_name, comment, expected)
   871  		return
   872  	}
   873  }
   874  
   875  func do_len_checks(t *testing.T, data map[string]any) {
   876  	if !check_length(t, data, "file_name_list", "file_map", 3) {
   877  		return
   878  	}
   879  	if !check_length(t, data, "message_name_list", "message_map", 7) {
   880  		return
   881  	}
   882  	if !check_length(t, data, "service_name_list", "service_map", 1) {
   883  		return
   884  	}
   885  	if !check_length(t, data, "enum_name_list", "enum_map", 2) {
   886  		return
   887  	}
   888  	if !check_length(t, data, "extension_name_list", "extension_map", 10) {
   889  		return
   890  	}
   891  
   892  }
   893  
   894  func get_sorted_keys(data map[string]any) []string {
   895  	keys := make([]string, 0, len(data))
   896  	for key := range data {
   897  		keys = append(keys, key)
   898  	}
   899  
   900  	sort.Strings(keys)
   901  	return keys
   902  }
   903  
   904  func check_length(
   905  	t *testing.T,
   906  	data map[string]any,
   907  	list_field_name, map_field_name string,
   908  	expected_len int,
   909  ) bool {
   910  	name_list, ok := data[list_field_name].([]any)
   911  	if !ok {
   912  		t.Errorf("wrong type for %s: %T", list_field_name,
   913  			data[list_field_name])
   914  		return false
   915  	}
   916  	if len(name_list) != expected_len {
   917  		t.Errorf("wrong length for %s: got %d, expected %d: list=%v",
   918  			list_field_name, len(name_list), expected_len, name_list)
   919  		return false
   920  	}
   921  
   922  	name_map, ok := data[map_field_name].(map[string]any)
   923  	if !ok {
   924  		t.Errorf("wrong type for %s: %T", map_field_name, data[map_field_name])
   925  		return false
   926  	}
   927  	if len(name_map) != len(name_list) {
   928  		t.Errorf("length of %s does not match that of %s: %T",
   929  			list_field_name, map_field_name, len(name_map))
   930  		return false
   931  	}
   932  
   933  	return true
   934  }
   935  
   936  func do_setup(t *testing.T) (map[string]any, bool) {
   937  	cur_dir, err := os.Getwd()
   938  	if err != nil {
   939  		t.Errorf("couldn't get working directory: %s", err)
   940  		return nil, false
   941  	}
   942  	defer os.Chdir(cur_dir)
   943  
   944  	work_dir := path.Join(cur_dir, "data/proto1")
   945  	proto_dir := work_dir
   946  	bin_dir := path.Join(cur_dir, "../cmd/protoc-gen-docjson")
   947  	out_file_name := "docs.json"
   948  
   949  	// Could also use t.TempDir() here, which would get cleaned up automatically
   950  	// once the test is complete.
   951  	json_out_dir, err := os.MkdirTemp("", "test_protoc-gen-docjson_*")
   952  	if err != nil {
   953  		t.Errorf("couldn't create temporary directory: %s", err)
   954  		return nil, false
   955  	}
   956  	defer os.RemoveAll(json_out_dir)
   957  
   958  	json_out_path := path.Join(json_out_dir, out_file_name)
   959  
   960  	cmd := "/usr/bin/env"
   961  	args := []string{
   962  		fmt.Sprintf("PATH=%s:%s", bin_dir, os.Getenv("PATH")),
   963  		"protoc",
   964  		fmt.Sprintf("--docjson_out=%s", json_out_dir),
   965  		fmt.Sprintf("--docjson_opt=outfile=%s,proto=%s",
   966  			out_file_name, proto_dir),
   967  		fmt.Sprintf("-I%s", proto_dir),
   968  		"tester.proto", "service-tester.proto", "subdir/docstuff.proto",
   969  	}
   970  
   971  	os.Chdir(work_dir)
   972  	t.Logf("running cmd %s %s", cmd, args)
   973  	if err := exec.Command(cmd, args...).Run(); err != nil {
   974  		t.Errorf("protobuf compiler failed: %s", err)
   975  		return nil, false
   976  	}
   977  
   978  	in_fh, err := os.Open(json_out_path)
   979  	if err != nil {
   980  		t.Errorf("couldn't open JSON file %q: %s", json_out_path, err)
   981  		return nil, false
   982  	}
   983  	defer in_fh.Close()
   984  
   985  	json_bytes, err := io.ReadAll(in_fh)
   986  	if err != nil {
   987  		t.Errorf("couldn't read JSON data from %q: %s", json_out_path, err)
   988  		return nil, false
   989  	}
   990  
   991  	// Checkout JSON against expected output.
   992  	data := make(map[string]any)
   993  	err = json.Unmarshal(json_bytes, &data)
   994  	if err != nil {
   995  		t.Errorf("couldn't unmarshal JSON file into data structure: %s", err)
   996  		return nil, false
   997  	}
   998  
   999  	return data, true
  1000  }