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 }