kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/markedsource/markedsource_test.go (about)

     1  /*
     2   * Copyright 2016 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package markedsource
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  
    29  	"google.golang.org/protobuf/encoding/prototext"
    30  	"google.golang.org/protobuf/proto"
    31  
    32  	cpb "kythe.io/kythe/proto/common_go_proto"
    33  )
    34  
    35  var docPath = filepath.Join(os.Getenv("RUNFILES_DIR"), "io_kythe/kythe/cxx/doc/doc")
    36  
    37  type oracleResults struct {
    38  	SimpleIdentifier string
    39  	SimpleParams     []string
    40  }
    41  
    42  // runOracle executes the C++ doc utility with ms as its input.  The utility's
    43  // output is then parsed and returned.
    44  //
    45  // Example utility output:
    46  //
    47  //	      RenderSimpleIdentifier: "hello world"
    48  //	      RenderSimpleParams: "param"
    49  //	RenderSimpleQualifiedName-ID: ""
    50  //	RenderSimpleQualifiedName+ID: "hello world"
    51  func runOracle(t *testing.T, ms *cpb.MarkedSource) *oracleResults {
    52  	cmd := exec.Command(docPath, "--common_signatures")
    53  	// The doc utility expects its stdin to be a single text-format MarkedSource
    54  	// proto message.
    55  	opts := prototext.MarshalOptions{Multiline: false}
    56  	rec, err := opts.Marshal(ms)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	// The utility's output is a series of lines, one per rendering
    61  	out := &bytes.Buffer{}
    62  	cmd.Stdin = bytes.NewReader(rec)
    63  	cmd.Stdout = out
    64  	cmd.Stderr = os.Stderr
    65  	if err := cmd.Run(); err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	var res oracleResults
    69  	s := bufio.NewScanner(out)
    70  	for s.Scan() {
    71  		line := strings.TrimSpace(s.Text())
    72  		if ident := strings.TrimPrefix(line, "RenderSimpleIdentifier: "); ident != line {
    73  			res.SimpleIdentifier = strings.Trim(ident, `"`)
    74  		} else if param := strings.TrimPrefix(line, "RenderSimpleParams: "); param != line {
    75  			res.SimpleParams = append(res.SimpleParams, strings.Trim(param, `"`))
    76  		} else {
    77  			t.Logf("Skipping doc line: %q", line)
    78  		}
    79  	}
    80  	if err := s.Err(); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	return &res
    84  }
    85  
    86  // TestInteropt checks the Go MarkedSource renderer against the canonical C++
    87  // renderer.  Each test parses the C++ doc utility output and compares the
    88  // results with the native Go implementations.
    89  func TestInteropt(t *testing.T) {
    90  	if os.Getenv("TEST_WORKSPACE") != "io_kythe" {
    91  		// Skip test since it requires the C++ oracle program to be put inside this
    92  		// test's Bazel RUNFILES_DIR.
    93  		t.Skip("Skipping test outside of Bazel build")
    94  	}
    95  	tests := []*cpb.MarkedSource{{
    96  		Kind:     cpb.MarkedSource_IDENTIFIER,
    97  		PreText:  "hello",
    98  		PostText: " world",
    99  	}, {
   100  		Kind: cpb.MarkedSource_BOX,
   101  		Child: []*cpb.MarkedSource{{
   102  			Kind:    cpb.MarkedSource_IDENTIFIER,
   103  			PreText: "ident",
   104  		}},
   105  	}, {
   106  		Kind: cpb.MarkedSource_PARAMETER,
   107  		Child: []*cpb.MarkedSource{{
   108  			Kind:    cpb.MarkedSource_IDENTIFIER,
   109  			PreText: "parameter",
   110  		}},
   111  	}, {
   112  		Kind: cpb.MarkedSource_BOX,
   113  		Child: []*cpb.MarkedSource{{
   114  			Kind:     cpb.MarkedSource_CONTEXT,
   115  			PreText:  "context",
   116  			PostText: ".",
   117  			Child: []*cpb.MarkedSource{{
   118  				Kind:    cpb.MarkedSource_IDENTIFIER,
   119  				PreText: "funcName",
   120  			}},
   121  		}, {
   122  			Kind: cpb.MarkedSource_PARAMETER,
   123  			Child: []*cpb.MarkedSource{{
   124  				Kind:    cpb.MarkedSource_IDENTIFIER,
   125  				PreText: "paramA",
   126  			}, {
   127  				Kind:    cpb.MarkedSource_IDENTIFIER,
   128  				PreText: "paramB",
   129  			}},
   130  		}},
   131  	}, {
   132  		Kind: cpb.MarkedSource_BOX,
   133  		Child: []*cpb.MarkedSource{{
   134  			Kind: cpb.MarkedSource_TYPE,
   135  			Child: []*cpb.MarkedSource{{
   136  				Kind:    cpb.MarkedSource_IDENTIFIER,
   137  				PreText: "int ",
   138  			}},
   139  		}, {
   140  			Kind:              cpb.MarkedSource_CONTEXT,
   141  			PostChildText:     ".",
   142  			AddFinalListToken: true,
   143  			Child: []*cpb.MarkedSource{{
   144  				Kind:    cpb.MarkedSource_IDENTIFIER,
   145  				PreText: "pkg",
   146  			}, {
   147  				Kind:    cpb.MarkedSource_IDENTIFIER,
   148  				PreText: "Files",
   149  			}},
   150  		}, {
   151  			Kind:    cpb.MarkedSource_IDENTIFIER,
   152  			PreText: "CONSTANT",
   153  		}},
   154  	}, {
   155  		Kind: cpb.MarkedSource_PARAMETER,
   156  		Child: []*cpb.MarkedSource{{
   157  			Kind:    cpb.MarkedSource_TYPE,
   158  			PreText: "*pkg.receiver",
   159  		}, {
   160  			Kind:          cpb.MarkedSource_BOX,
   161  			PostChildText: " ",
   162  			Child: []*cpb.MarkedSource{{
   163  				Kind: cpb.MarkedSource_BOX,
   164  				Child: []*cpb.MarkedSource{{
   165  					Kind:    cpb.MarkedSource_CONTEXT,
   166  					PreText: "pkg",
   167  				}, {
   168  					Kind:    cpb.MarkedSource_IDENTIFIER,
   169  					PreText: "param",
   170  				}},
   171  			}, {
   172  				Kind:    cpb.MarkedSource_TYPE,
   173  				PreText: "string",
   174  			}},
   175  		}},
   176  	}}
   177  
   178  	for i, test := range tests {
   179  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   180  			oracle := runOracle(t, test)
   181  			if ident := RenderSimpleIdentifier(test, PlaintextContent, nil); oracle.SimpleIdentifier != ident {
   182  				t.Errorf("RenderSimpleIdentifier({%+v}): expected: %q; found %q", test, oracle.SimpleIdentifier, ident)
   183  			}
   184  			params := RenderSimpleParams(test, PlaintextContent, nil)
   185  			if len(params) != len(oracle.SimpleParams) {
   186  				t.Errorf("RenderSimpleParams({%+v}); expected: %#v; found: %#v", test, oracle.SimpleParams, params)
   187  			} else {
   188  				for i, expected := range oracle.SimpleParams {
   189  					if expected != params[i] {
   190  						t.Errorf("RenderSimpleParams({%+v})[%d]; expected: %#v; found: %#v", test, i, expected, params[i])
   191  					}
   192  				}
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestRender(t *testing.T) {
   199  	tests := []struct {
   200  		in  *cpb.MarkedSource
   201  		out string
   202  	}{
   203  		{&cpb.MarkedSource{}, ""},
   204  		{&cpb.MarkedSource{PreText: "PRE", PostText: "POST"}, "PREPOST"},
   205  		{&cpb.MarkedSource{PostChildText: ","}, ""},
   206  		{&cpb.MarkedSource{PostChildText: ",", AddFinalListToken: true}, ""},
   207  		{&cpb.MarkedSource{PreText: "PRE", PostText: "POST", PostChildText: ",",
   208  			Child: []*cpb.MarkedSource{{PreText: "C1"}}}, "PREC1POST"},
   209  		{&cpb.MarkedSource{PreText: "PRE", PostText: "POST", PostChildText: ",", AddFinalListToken: true,
   210  			Child: []*cpb.MarkedSource{{PreText: "C1"}}}, "PREC1,POST"},
   211  		{&cpb.MarkedSource{PreText: "PRE", PostText: "POST", PostChildText: ",",
   212  			Child: []*cpb.MarkedSource{{PreText: "C1"}, {PreText: "C2"}}}, "PREC1,C2POST"},
   213  		{&cpb.MarkedSource{PreText: "PRE", PostText: "POST", PostChildText: ",", AddFinalListToken: true,
   214  			Child: []*cpb.MarkedSource{{PreText: "C1"}, {PreText: "C2"}}}, "PREC1,C2,POST"},
   215  	}
   216  	for i, test := range tests {
   217  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   218  			if got := Render(test.in); got != test.out {
   219  				t.Errorf("from %v: got %q, expected %q", test.in, got, test.out)
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestQName(t *testing.T) {
   226  	// Tests transliterated from QualifiedNameExtractorTest.java.
   227  	tests := []struct {
   228  		input string // text-format proto
   229  		want  *cpb.SymbolInfo
   230  	}{
   231  		{input: "child {\nkind: CONTEXT\nchild {\nkind: IDENTIFIER\npre_text: \"java\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"com\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"google\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"devtools\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"kythe\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"analyzers\"\n} \nchild {\nkind: IDENTIFIER\npre_text: \"java\"\n} \npost_child_text: \".\"\nadd_final_list_token: true\n} \nchild {\nkind: IDENTIFIER\npre_text: \"JavaEntrySets\"\n}",
   232  
   233  			want: &cpb.SymbolInfo{BaseName: "JavaEntrySets", QualifiedName: "java.com.google.devtools.kythe.analyzers.java.JavaEntrySets"}},
   234  
   235  		{input: "child {\nkind: CONTEXT \npost_child_text: \".\"\nadd_final_list_token: true\n} \nchild {\nkind: IDENTIFIER\npre_text: \"JavaEntrySets\"\n}",
   236  			want: &cpb.SymbolInfo{BaseName: "JavaEntrySets"}},
   237  
   238  		{input: "child {\nchild {\nkind: IDENTIFIER\npre_text: \"JavaEntrySets\"\n}\n}",
   239  			want: &cpb.SymbolInfo{BaseName: "JavaEntrySets"}},
   240  
   241  		{input: "child {}", want: new(cpb.SymbolInfo)},
   242  
   243  		{input: "child { pre_text: \"type \" } child { child { kind: CONTEXT child { kind: IDENTIFIER pre_text: \"kythe/go/platform/kindex\" } post_child_text: \".\" add_final_list_token: true } child { kind: IDENTIFIER pre_text: \"Settings\" } } child { kind: TYPE pre_text: \" \" } child { kind: TYPE pre_text: \"struct {...}\" }",
   244  			want: &cpb.SymbolInfo{BaseName: "Settings", QualifiedName: "kythe/go/platform/kindex.Settings"}},
   245  
   246  		{input: "child: {\n  pre_text: \"func \"\n}\nchild: {\n  kind: PARAMETER\n  pre_text: \"(\"\n  child: {\n    kind: TYPE\n    pre_text: \"*w\"\n  }\n  post_text: \") \"\n}\nchild: {\n  child: {\n    kind: CONTEXT\n    child: {\n      kind: IDENTIFIER\n      pre_text: \"methdecl\"\n    }\n    child: {\n      kind: IDENTIFIER\n      pre_text: \"w\"\n    }\n    post_child_text: \".\"\n    add_final_list_token: true\n  }\n  child: {\n    kind: IDENTIFIER\n    pre_text: \"LessThan\"\n  }\n}\nchild: {\n  kind: PARAMETER_LOOKUP_BY_PARAM\n  pre_text: \"(\"\n  post_child_text: \", \"\n  post_text: \")\"\n  lookup_index: 1\n}\nchild: {\n  pre_text: \" \"\n  child: {\n    pre_text: \"bool\"\n  }\n}",
   247  			want: &cpb.SymbolInfo{BaseName: "LessThan", QualifiedName: "methdecl.w.LessThan"}},
   248  
   249  		// Verify that a default separator does not get injected at the end.
   250  		{input: `child { kind: CONTEXT child { kind: IDENTIFIER pre_text: "//kythe/proto" } } child { kind: IDENTIFIER pre_text: ":analysis_go_proto" }`,
   251  			want: &cpb.SymbolInfo{BaseName: ":analysis_go_proto", QualifiedName: "//kythe/proto:analysis_go_proto"}},
   252  
   253  		// Verify that the default separator is correctly used.
   254  		{input: `child { kind: CONTEXT child { kind: IDENTIFIER pre_text: "a" } child { kind: IDENTIFIER pre_text: "b" } } child { kind: IDENTIFIER pre_text: "-tail" }`,
   255  			want: &cpb.SymbolInfo{BaseName: "-tail", QualifiedName: "a.b-tail"}},
   256  
   257  		// Verify that template names in context can be correctly rendered.
   258  		{input: `child { kind: CONTEXT child { kind: IDENTIFIER pre_text: "a" } child { child { kind: IDENTIFIER pre_text: "b" } child: { pre_text: "<" child: { pre_text: "c" } post_text: ">"} } post_child_text: "::" } child { kind: IDENTIFIER pre_text: "d" }`,
   259  			want: &cpb.SymbolInfo{BaseName: "d", QualifiedName: "a::b<c>::d"}},
   260  
   261  		// Verify that a standalone identifier can work.
   262  		{input: `kind: IDENTIFIER pre_text: "hello"`,
   263  			want: &cpb.SymbolInfo{BaseName: "hello", QualifiedName: ""}},
   264  	}
   265  
   266  	for i, test := range tests {
   267  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   268  			var ms cpb.MarkedSource
   269  			if err := prototext.Unmarshal([]byte(test.input), &ms); err != nil {
   270  				t.Fatalf("Invalid test input: %v\nInput was %#q", err, test.input)
   271  			}
   272  
   273  			if got := RenderQualifiedName(&ms); !proto.Equal(got, test.want) {
   274  				t.Errorf("Invalid result: got %q, want %q\nInput was %#q", got, test.want, test.input)
   275  			}
   276  		})
   277  	}
   278  }