github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/cppcompile/flagsparser_test.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cppcompile 16 17 import ( 18 "context" 19 "path/filepath" 20 "testing" 21 22 "github.com/bazelbuild/reclient/internal/pkg/execroot" 23 "github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 ) 28 29 const ( 30 fakeExecRoot = "fake" 31 ) 32 33 func TestParseFlags(t *testing.T) { 34 er, cleanup := execroot.Setup(t, nil) 35 defer cleanup() 36 tests := []struct { 37 name string 38 command []string 39 workingDir string 40 files map[string][]byte 41 want *flags.CommandFlags 42 }{ 43 { 44 name: "Simple clang command", 45 workingDir: ".", 46 command: []string{"clang++", "-c", "test.cpp"}, 47 want: &flags.CommandFlags{ 48 ExecutablePath: "clang++", 49 Flags: []*flags.Flag{ 50 &flags.Flag{Key: "-c"}, 51 }, 52 TargetFilePaths: []string{"test.cpp"}, 53 WorkingDirectory: ".", 54 ExecRoot: er, 55 }, 56 }, 57 { 58 name: "Simple clang command with working directory", 59 workingDir: "foo", 60 command: []string{"clang++", "-c", "test.cpp"}, 61 want: &flags.CommandFlags{ 62 ExecutablePath: "clang++", 63 Flags: []*flags.Flag{ 64 &flags.Flag{Key: "-c"}, 65 }, 66 TargetFilePaths: []string{"test.cpp"}, 67 WorkingDirectory: "foo", 68 ExecRoot: er, 69 }, 70 }, 71 { 72 name: "Simple clang command with outputs", 73 workingDir: ".", 74 command: []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"}, 75 want: &flags.CommandFlags{ 76 ExecutablePath: "clang++", 77 Flags: []*flags.Flag{ 78 &flags.Flag{Key: "-c"}, 79 }, 80 TargetFilePaths: []string{"test.cpp"}, 81 EmittedDependencyFile: "test.d", 82 WorkingDirectory: ".", 83 ExecRoot: er, 84 OutputFilePaths: []string{"test.o", "test.d"}, 85 }, 86 }, 87 { 88 name: "Simple clang command with rsp file", 89 workingDir: ".", 90 command: []string{"clang++", "@rsp", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"}, 91 want: &flags.CommandFlags{ 92 ExecutablePath: "clang++", 93 Flags: []*flags.Flag{ 94 &flags.Flag{Key: "-c"}, 95 }, 96 TargetFilePaths: []string{"test.cpp"}, 97 EmittedDependencyFile: "test.d", 98 WorkingDirectory: ".", 99 ExecRoot: er, 100 Dependencies: []string{"rsp"}, 101 OutputFilePaths: []string{"test.o", "test.d"}, 102 }, 103 }, 104 { 105 name: "Simple clang command with outputs and working directory", 106 workingDir: "foo", 107 command: []string{"clang++", "-c", "-o", "test.o", "-MF", "test.d", "test.cpp"}, 108 want: &flags.CommandFlags{ 109 ExecutablePath: "clang++", 110 Flags: []*flags.Flag{ 111 &flags.Flag{Key: "-c"}, 112 }, 113 TargetFilePaths: []string{"test.cpp"}, 114 EmittedDependencyFile: "test.d", 115 WorkingDirectory: "foo", 116 ExecRoot: er, 117 OutputFilePaths: []string{"test.o", "test.d"}, 118 }, 119 }, 120 { 121 name: "Clang command with an fprofile file", 122 workingDir: ".", 123 command: []string{"clang++", "-c", "-o", "test.o", "-fprofile-use=prof.txt", "-MF", "test.d", "test.cpp"}, 124 want: &flags.CommandFlags{ 125 ExecutablePath: "clang++", 126 Flags: []*flags.Flag{ 127 &flags.Flag{Key: "-c"}, 128 &flags.Flag{Key: "-fprofile-use=", Value: "prof.txt", Joined: true}, 129 }, 130 TargetFilePaths: []string{"test.cpp"}, 131 EmittedDependencyFile: "test.d", 132 Dependencies: []string{"prof.txt"}, 133 WorkingDirectory: ".", 134 ExecRoot: er, 135 OutputFilePaths: []string{"test.o", "test.d"}, 136 }, 137 }, 138 { 139 name: "Clang command with an fprofile-list file", 140 workingDir: ".", 141 command: []string{"clang++", "-c", "-o", "test.o", "-fprofile-list=fun1.list", "-fprofile-list=fun2.list", "-MF", "test.d", "test.cpp"}, 142 want: &flags.CommandFlags{ 143 ExecutablePath: "clang++", 144 Flags: []*flags.Flag{ 145 &flags.Flag{Key: "-c"}, 146 &flags.Flag{Key: "-fprofile-list=", Value: "fun1.list", Joined: true}, 147 &flags.Flag{Key: "-fprofile-list=", Value: "fun2.list", Joined: true}, 148 }, 149 TargetFilePaths: []string{"test.cpp"}, 150 EmittedDependencyFile: "test.d", 151 Dependencies: []string{"fun1.list", "fun2.list"}, 152 WorkingDirectory: ".", 153 ExecRoot: er, 154 OutputFilePaths: []string{"test.o", "test.d"}, 155 }, 156 }, 157 { 158 name: "Clang command with explicit coverage file", 159 workingDir: ".", 160 command: []string{"clang++", "-c", "-o", "test.o", "--coverage-data-file=foo.gcno", "-MF", "test.d", "test.cpp"}, 161 want: &flags.CommandFlags{ 162 ExecutablePath: "clang++", 163 Flags: []*flags.Flag{ 164 &flags.Flag{Key: "-c"}, 165 &flags.Flag{Key: "--coverage-data-file=", Value: "foo.gcno", Joined: true}, 166 }, 167 TargetFilePaths: []string{"test.cpp"}, 168 EmittedDependencyFile: "test.d", 169 WorkingDirectory: ".", 170 ExecRoot: er, 171 OutputFilePaths: []string{"test.o", "foo.gcno", "test.d"}, 172 }, 173 }, 174 { 175 name: "Clang command with coverage", 176 workingDir: ".", 177 command: []string{"clang++", "-c", "-o", "test.o", "--coverage", "-MF", "test.d", "test.cpp"}, 178 want: &flags.CommandFlags{ 179 ExecutablePath: "clang++", 180 Flags: []*flags.Flag{ 181 &flags.Flag{Key: "-c"}, 182 &flags.Flag{Key: "--coverage"}, 183 }, 184 TargetFilePaths: []string{"test.cpp"}, 185 EmittedDependencyFile: "test.d", 186 WorkingDirectory: ".", 187 ExecRoot: er, 188 OutputFilePaths: []string{"test.o", "test.d", "test.gcno"}, 189 }, 190 }, 191 { 192 name: "Clang command with coverage before .o", 193 workingDir: ".", 194 command: []string{"clang++", "-c", "-ftest-coverage", "-o", "test.o", "-MF", "test.d", "test.cpp"}, 195 want: &flags.CommandFlags{ 196 ExecutablePath: "clang++", 197 Flags: []*flags.Flag{ 198 &flags.Flag{Key: "-c"}, 199 &flags.Flag{Key: "-ftest-coverage"}, 200 }, 201 TargetFilePaths: []string{"test.cpp"}, 202 EmittedDependencyFile: "test.d", 203 WorkingDirectory: ".", 204 ExecRoot: er, 205 OutputFilePaths: []string{"test.o", "test.d", "test.gcno"}, 206 }, 207 }, 208 { 209 name: "Clang command with gsplit-dwarf", 210 workingDir: ".", 211 command: []string{"clang++", "-c", "-o", "test.o", "-gsplit-dwarf", "-MF", "test.d", "test.cpp"}, 212 want: &flags.CommandFlags{ 213 ExecutablePath: "clang++", 214 Flags: []*flags.Flag{ 215 &flags.Flag{Key: "-c"}, 216 &flags.Flag{Key: "-gsplit-dwarf"}, 217 }, 218 TargetFilePaths: []string{"test.cpp"}, 219 EmittedDependencyFile: "test.d", 220 WorkingDirectory: ".", 221 ExecRoot: er, 222 OutputFilePaths: []string{"test.o", "test.d", "test.dwo"}, 223 }, 224 }, 225 { 226 name: "Clang command with an fsanitize-blacklist file", 227 workingDir: ".", 228 command: []string{"clang++", "-c", "-o", "test.o", "-fsanitize-blacklist=blacklist.txt", "-MF", "test.d", "test.cpp"}, 229 want: &flags.CommandFlags{ 230 ExecutablePath: "clang++", 231 Flags: []*flags.Flag{ 232 &flags.Flag{Key: "-c"}, 233 &flags.Flag{Key: "-fsanitize-blacklist=", Value: "blacklist.txt", Joined: true}, 234 }, 235 TargetFilePaths: []string{"test.cpp"}, 236 EmittedDependencyFile: "test.d", 237 Dependencies: []string{"blacklist.txt"}, 238 WorkingDirectory: ".", 239 ExecRoot: er, 240 OutputFilePaths: []string{"test.o", "test.d"}, 241 }, 242 }, 243 { 244 name: "Clang command with an fsanitize-ignorelist file", 245 workingDir: ".", 246 command: []string{"clang++", "-c", "-o", "test.o", "-fsanitize-ignorelist=ignorelist.txt", "-MF", "test.d", "test.cpp"}, 247 want: &flags.CommandFlags{ 248 ExecutablePath: "clang++", 249 Flags: []*flags.Flag{ 250 &flags.Flag{Key: "-c"}, 251 &flags.Flag{Key: "-fsanitize-ignorelist=", Value: "ignorelist.txt", Joined: true}, 252 }, 253 TargetFilePaths: []string{"test.cpp"}, 254 EmittedDependencyFile: "test.d", 255 Dependencies: []string{"ignorelist.txt"}, 256 WorkingDirectory: ".", 257 ExecRoot: er, 258 OutputFilePaths: []string{"test.o", "test.d"}, 259 }, 260 }, 261 { 262 name: "Clang command with an fprofile-sample-use file", 263 workingDir: ".", 264 command: []string{"clang++", "-c", "-o", "test.o", "-fprofile-sample-use=sample.txt", "-MF", "test.d", "test.cpp"}, 265 want: &flags.CommandFlags{ 266 ExecutablePath: "clang++", 267 Flags: []*flags.Flag{ 268 &flags.Flag{Key: "-c"}, 269 &flags.Flag{Key: "-fprofile-sample-use=", Value: "sample.txt", Joined: true}, 270 }, 271 TargetFilePaths: []string{"test.cpp"}, 272 EmittedDependencyFile: "test.d", 273 Dependencies: []string{"sample.txt"}, 274 WorkingDirectory: ".", 275 ExecRoot: er, 276 OutputFilePaths: []string{"test.o", "test.d"}, 277 }, 278 }, 279 { 280 name: "Clang command with a -B<dir> flag", 281 workingDir: ".", 282 command: []string{"clang++", "-c", "-o", "test.o", "-Bprebuilts/gcc/linux-x86/bin/", "-MF", "test.d", "test.cpp"}, 283 want: &flags.CommandFlags{ 284 ExecutablePath: "clang++", 285 Flags: []*flags.Flag{ 286 &flags.Flag{Key: "-c"}, 287 &flags.Flag{Key: "-B", Value: "prebuilts/gcc/linux-x86/bin/", Joined: true}, 288 }, 289 TargetFilePaths: []string{"test.cpp"}, 290 EmittedDependencyFile: "test.d", 291 Dependencies: []string{"prebuilts/gcc/linux-x86/bin/"}, 292 WorkingDirectory: ".", 293 ExecRoot: er, 294 OutputFilePaths: []string{"test.o", "test.d"}, 295 }, 296 }, 297 { 298 name: "Clang command with a -B <dir> flag", 299 workingDir: ".", 300 command: []string{"clang++", "-c", "-o", "test.o", "-B", "prebuilts/gcc/linux-x86/bin/", "-MF", "test.d", "test.cpp"}, 301 want: &flags.CommandFlags{ 302 ExecutablePath: "clang++", 303 Flags: []*flags.Flag{ 304 &flags.Flag{Key: "-c"}, 305 &flags.Flag{Key: "-B", Value: "prebuilts/gcc/linux-x86/bin/"}, 306 }, 307 TargetFilePaths: []string{"test.cpp"}, 308 EmittedDependencyFile: "test.d", 309 Dependencies: []string{"prebuilts/gcc/linux-x86/bin/"}, 310 WorkingDirectory: ".", 311 ExecRoot: er, 312 OutputFilePaths: []string{"test.o", "test.d"}, 313 }, 314 }, 315 { 316 name: "Clang command with include directories", 317 workingDir: ".", 318 command: []string{ 319 "clang++", 320 "-Iincludes/bla", 321 "-I", 322 "moreincludes", 323 "-Iincludes/boo", 324 "-I", 325 "includes/bla", 326 "-c", 327 "-o", 328 "test.o", 329 "test.cpp", 330 }, 331 want: &flags.CommandFlags{ 332 ExecutablePath: "clang++", 333 Flags: []*flags.Flag{ 334 &flags.Flag{Key: "-I", Value: "includes/bla", Joined: true}, 335 &flags.Flag{Key: "-I", Value: "moreincludes"}, 336 &flags.Flag{Key: "-I", Value: "includes/boo", Joined: true}, 337 &flags.Flag{Key: "-I", Value: "includes/bla"}, 338 &flags.Flag{Key: "-c"}, 339 }, 340 TargetFilePaths: []string{"test.cpp"}, 341 IncludeDirPaths: []string{"includes/bla", "moreincludes", "includes/boo", "includes/bla"}, 342 WorkingDirectory: ".", 343 ExecRoot: er, 344 OutputFilePaths: []string{"test.o"}, 345 }, 346 }, 347 { 348 name: "Clang command with include directories and working directory", 349 workingDir: "foo", 350 command: []string{ 351 "clang++", 352 "-Iincludes/bla", 353 "-I", 354 "moreincludes", 355 "-Iincludes/boo", 356 "-I", 357 "includes/bla", 358 "-c", 359 "-o", 360 "test.o", 361 "test.cpp", 362 }, 363 want: &flags.CommandFlags{ 364 ExecutablePath: "clang++", 365 Flags: []*flags.Flag{ 366 {Key: "-I", Value: "includes/bla", Joined: true}, 367 {Key: "-I", Value: "moreincludes"}, 368 {Key: "-I", Value: "includes/boo", Joined: true}, 369 {Key: "-I", Value: "includes/bla"}, 370 {Key: "-c"}, 371 }, 372 TargetFilePaths: []string{"test.cpp"}, 373 IncludeDirPaths: []string{"includes/bla", "moreincludes", "includes/boo", "includes/bla"}, 374 WorkingDirectory: "foo", 375 ExecRoot: er, 376 OutputFilePaths: []string{"test.o"}, 377 }, 378 }, 379 { 380 name: "Clang command with include directories, working directory and arguments with parameter", 381 workingDir: "foo", 382 command: []string{ 383 "clang++", 384 "-Iincludes/bla", 385 "-I", 386 "moreincludes", 387 "-Iincludes/boo", 388 "-I", 389 "includes/bla", 390 "-DBAR", 391 "-D", 392 "BAZ", 393 "-fmax-tokens", 394 "32", 395 "-c", 396 "-o", 397 "test.o", 398 "test.cpp", 399 }, 400 want: &flags.CommandFlags{ 401 ExecutablePath: "clang++", 402 Flags: []*flags.Flag{ 403 &flags.Flag{Key: "-I", Value: "includes/bla", Joined: true}, 404 &flags.Flag{Key: "-I", Value: "moreincludes"}, 405 &flags.Flag{Key: "-I", Value: "includes/boo", Joined: true}, 406 &flags.Flag{Key: "-I", Value: "includes/bla"}, 407 &flags.Flag{Key: "-D", Value: "BAR", Joined: true}, 408 &flags.Flag{Key: "-D", Value: "BAZ"}, 409 &flags.Flag{Key: "-fmax-tokens", Value: "32"}, 410 &flags.Flag{Key: "-c"}, 411 }, 412 TargetFilePaths: []string{"test.cpp"}, 413 IncludeDirPaths: []string{"includes/bla", "moreincludes", "includes/boo", "includes/bla"}, 414 WorkingDirectory: "foo", 415 ExecRoot: er, 416 OutputFilePaths: []string{"test.o"}, 417 }, 418 }, 419 { 420 name: "Clang command with include directories, working directory and arguments with parameter, target file not last", 421 workingDir: "foo", 422 command: []string{ 423 "clang++", 424 "-Iincludes/bla", 425 "-I", 426 "moreincludes", 427 "-Iincludes/boo", 428 "-I", 429 "includes/bla", 430 "-DBAR", 431 "-D", 432 "BAZ", 433 "-fmax-tokens", 434 "32", 435 "-c", 436 "test.cpp", 437 "-o", 438 "test.o", 439 }, 440 want: &flags.CommandFlags{ 441 ExecutablePath: "clang++", 442 Flags: []*flags.Flag{ 443 &flags.Flag{Key: "-I", Value: "includes/bla", Joined: true}, 444 &flags.Flag{Key: "-I", Value: "moreincludes"}, 445 &flags.Flag{Key: "-I", Value: "includes/boo", Joined: true}, 446 &flags.Flag{Key: "-I", Value: "includes/bla"}, 447 &flags.Flag{Key: "-D", Value: "BAR", Joined: true}, 448 &flags.Flag{Key: "-D", Value: "BAZ"}, 449 &flags.Flag{Key: "-fmax-tokens", Value: "32"}, 450 &flags.Flag{Key: "-c"}, 451 }, 452 TargetFilePaths: []string{"test.cpp"}, 453 IncludeDirPaths: []string{"includes/bla", "moreincludes", "includes/boo", "includes/bla"}, 454 WorkingDirectory: "foo", 455 ExecRoot: er, 456 OutputFilePaths: []string{"test.o"}, 457 }, 458 }, 459 { 460 name: "ThinLTO clang command", 461 workingDir: ".", 462 command: []string{"clang", "-c", "-fthinlto-index=foo.o.thinlto.bc", "test.cpp"}, 463 want: &flags.CommandFlags{ 464 ExecutablePath: "clang", 465 Flags: []*flags.Flag{ 466 &flags.Flag{Key: "-c"}, 467 &flags.Flag{Key: "-fthinlto-index=", Value: "foo.o.thinlto.bc", Joined: true}, 468 }, 469 TargetFilePaths: []string{"test.cpp"}, 470 Dependencies: []string{"foo.o.thinlto.bc"}, 471 WorkingDirectory: ".", 472 ExecRoot: er, 473 }, 474 }, { 475 name: "ThinLTO clang command with working dir", 476 workingDir: "out", 477 command: []string{"clang", "-c", "-fthinlto-index=lto.foo/foo.o.thinlto.bc", "test.cpp"}, 478 want: &flags.CommandFlags{ 479 ExecutablePath: "clang", 480 Flags: []*flags.Flag{ 481 &flags.Flag{Key: "-c"}, 482 &flags.Flag{Key: "-fthinlto-index=", Value: "lto.foo/foo.o.thinlto.bc", Joined: true}, 483 }, 484 TargetFilePaths: []string{"test.cpp"}, 485 Dependencies: []string{"lto.foo/foo.o.thinlto.bc"}, 486 WorkingDirectory: "out", 487 ExecRoot: er, 488 }, 489 }, 490 { 491 name: "Clang command with -save-temps", 492 workingDir: ".", 493 command: []string{"clang++", "-o", "out/objFile.o", "-save-temps", "source.cpp"}, 494 want: &flags.CommandFlags{ 495 ExecutablePath: "clang++", 496 Flags: []*flags.Flag{ 497 &flags.Flag{Key: "--save-temps"}, 498 }, 499 TargetFilePaths: []string{"source.cpp"}, 500 WorkingDirectory: ".", 501 ExecRoot: er, 502 OutputFilePaths: []string{"out/objFile.o", "source.i", "source.ii", "source.bc", "source.s"}, 503 }, 504 }, 505 { 506 name: "Clang command with -save-temps=obj", 507 workingDir: ".", 508 command: []string{"clang++", "-o", "out/objFile.o", "-save-temps=obj", "source.cpp"}, 509 want: &flags.CommandFlags{ 510 ExecutablePath: "clang++", 511 Flags: []*flags.Flag{ 512 &flags.Flag{Key: "--save-temps=", Value: "obj", Joined: true}, 513 }, 514 TargetFilePaths: []string{"source.cpp"}, 515 WorkingDirectory: ".", 516 ExecRoot: er, 517 OutputFilePaths: []string{"out/objFile.o", filepath.Join("out", "source.i"), filepath.Join("out", "source.ii"), 518 filepath.Join("out", "source.bc"), filepath.Join("out", "source.s")}, 519 }, 520 }, 521 { 522 name: "Clang command with -save-temps=obj without -o", 523 workingDir: ".", 524 command: []string{"clang++", "-save-temps=obj", "source.cpp"}, 525 want: &flags.CommandFlags{ 526 ExecutablePath: "clang++", 527 Flags: []*flags.Flag{ 528 &flags.Flag{Key: "--save-temps=", Value: "obj", Joined: true}, 529 }, 530 TargetFilePaths: []string{"source.cpp"}, 531 WorkingDirectory: ".", 532 ExecRoot: er, 533 OutputFilePaths: []string{"source.i", "source.ii", "source.bc", "source.s"}, 534 }, 535 }, 536 { 537 name: "Clang command with -save-temps=foo", 538 workingDir: ".", 539 command: []string{"clang++", "-o", "out/objFile.o", "-save-temps=foo", "source.cpp"}, 540 want: &flags.CommandFlags{ 541 ExecutablePath: "clang++", 542 Flags: []*flags.Flag{ 543 &flags.Flag{Key: "--save-temps=", Value: "foo", Joined: true}, 544 }, 545 TargetFilePaths: []string{"source.cpp"}, 546 WorkingDirectory: ".", 547 ExecRoot: er, 548 OutputFilePaths: []string{"out/objFile.o", "source.i", "source.ii", "source.bc", "source.s"}, 549 }, 550 }, 551 } 552 553 for _, test := range tests { 554 t.Run(test.name, func(t *testing.T) { 555 ctx := context.Background() 556 execroot.AddFilesWithContent(t, er, test.files) 557 got, err := ClangParser{}.ParseFlags(ctx, test.command, test.workingDir, er) 558 if err != nil { 559 t.Errorf("ParseFlags(%v,%v,%v).err = %v, want no error.", test.command, test.workingDir, er, err) 560 } 561 562 if diff := cmp.Diff(test.want, got, cmpopts.IgnoreUnexported(flags.Flag{})); diff != "" { 563 t.Errorf("Test %v, ParseFlags(%v,%v,%v) returned diff, (-want +got): %s", test.name, test.command, test.workingDir, er, diff) 564 } 565 }) 566 } 567 }