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  }