github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/clanglink/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 clanglink
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"testing"
    21  
    22  	"github.com/bazelbuild/reclient/internal/pkg/execroot"
    23  	"github.com/bazelbuild/reclient/internal/pkg/inputprocessor"
    24  	"github.com/bazelbuild/reclient/internal/pkg/inputprocessor/flags"
    25  
    26  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  )
    30  
    31  func TestClangLinkParser(t *testing.T) {
    32  	er, cleanup := execroot.Setup(t, nil)
    33  	defer cleanup()
    34  	test := []struct {
    35  		name          string
    36  		command       []string
    37  		existingFiles map[string][]byte
    38  		want          *flags.CommandFlags
    39  	}{
    40  		{
    41  			name:    "Clang link command with a --sysroot <dir> flag, added as dependency",
    42  			command: []string{"clang++", "-c", "-o", "test.o", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "test.cpp"},
    43  			want: &flags.CommandFlags{
    44  				ExecutablePath: "clang++",
    45  				Flags: []*flags.Flag{
    46  					&flags.Flag{Value: "-c"},
    47  					&flags.Flag{Value: "--sysroot"},
    48  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
    49  					&flags.Flag{Value: "test.cpp"},
    50  				},
    51  				Dependencies: []string{
    52  					"prebuilts/gcc/linux-x86/bin/",
    53  					"test.cpp",
    54  				},
    55  				ExecRoot:        er,
    56  				OutputFilePaths: []string{"test.o"},
    57  			},
    58  		},
    59  		{
    60  			name:    "Clang link command with a --sysroot=<dir> flag, added as dependency",
    61  			command: []string{"clang++", "-c", "-o", "test.o", "--sysroot=prebuilts/gcc/linux-x86/bin/", "test.cpp"},
    62  			want: &flags.CommandFlags{
    63  				ExecutablePath: "clang++",
    64  				Flags: []*flags.Flag{
    65  					&flags.Flag{Value: "-c"},
    66  					&flags.Flag{Value: "--sysroot=prebuilts/gcc/linux-x86/bin/"},
    67  					&flags.Flag{Value: "test.cpp"},
    68  				},
    69  				Dependencies: []string{
    70  					"prebuilts/gcc/linux-x86/bin/",
    71  					"test.cpp",
    72  				},
    73  				ExecRoot:        er,
    74  				OutputFilePaths: []string{"test.o"},
    75  			},
    76  		},
    77  		{
    78  			name:          "Clang link command with an rsp file specified with @ arg, file contents added as dependency",
    79  			command:       []string{"clang++", "-fuse-ld=lld", "-o", "test", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "@test.rsp"},
    80  			existingFiles: map[string][]byte{"test.rsp": []byte("test.o")},
    81  			want: &flags.CommandFlags{
    82  				ExecutablePath: "clang++",
    83  				Flags: []*flags.Flag{
    84  					&flags.Flag{Value: "-fuse-ld=lld"},
    85  					&flags.Flag{Value: "--sysroot"},
    86  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
    87  					&flags.Flag{Value: "@test.rsp"},
    88  				},
    89  				Dependencies: []string{
    90  					"prebuilts/gcc/linux-x86/bin/",
    91  					"test.rsp",
    92  					"test.o",
    93  				},
    94  				ExecRoot:        er,
    95  				OutputFilePaths: []string{"test"},
    96  			},
    97  		},
    98  		{
    99  			name:    "Clang link command with an archive which is scanned and contents added as dependency",
   100  			command: []string{"clang++", "-fuse-ld=lld", "-o", "test", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "testarchive.a"},
   101  
   102  			existingFiles: map[string][]byte{
   103  				"testarchive.a": []byte(
   104  					"!<arch>\n" +
   105  						"foo.o           0           0     0     644     4         `\n" +
   106  						"foo\n" +
   107  						"bar.o           0           0     0     644     4         `\n" +
   108  						"bar\n" +
   109  						"baz.o           0           0     0     644     4         `\n" +
   110  						"baz",
   111  				),
   112  			},
   113  			want: &flags.CommandFlags{
   114  				ExecutablePath: "clang++",
   115  				Flags: []*flags.Flag{
   116  					&flags.Flag{Value: "-fuse-ld=lld"},
   117  					&flags.Flag{Value: "--sysroot"},
   118  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
   119  					&flags.Flag{Value: "testarchive.a"},
   120  				},
   121  				Dependencies: []string{
   122  					"prebuilts/gcc/linux-x86/bin/",
   123  					"foo.o",
   124  					"bar.o",
   125  					"baz.o",
   126  					"testarchive.a",
   127  				},
   128  				ExecRoot:        er,
   129  				OutputFilePaths: []string{"test"},
   130  			},
   131  		},
   132  		{
   133  			name:    "Clang link command with an rsp file specified with @ arg, which contains archive which is scanned and contents added as dependency",
   134  			command: []string{"clang++", "-fuse-ld=lld", "-o", "test", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "@test.rsp"},
   135  
   136  			existingFiles: map[string][]byte{
   137  				"test.rsp": []byte("testarchive.a"),
   138  				"testarchive.a": []byte(
   139  					"!<arch>\n" +
   140  						"foo.o           0           0     0     644     4         `\n" +
   141  						"foo\n" +
   142  						"bar.o           0           0     0     644     4         `\n" +
   143  						"bar\n" +
   144  						"baz.o           0           0     0     644     4         `\n" +
   145  						"baz",
   146  				),
   147  			},
   148  			want: &flags.CommandFlags{
   149  				ExecutablePath: "clang++",
   150  				Flags: []*flags.Flag{
   151  					&flags.Flag{Value: "-fuse-ld=lld"},
   152  					&flags.Flag{Value: "--sysroot"},
   153  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
   154  					&flags.Flag{Value: "@test.rsp"},
   155  				},
   156  				Dependencies: []string{
   157  					"prebuilts/gcc/linux-x86/bin/",
   158  					"test.rsp",
   159  					"foo.o",
   160  					"bar.o",
   161  					"baz.o",
   162  					"testarchive.a",
   163  				},
   164  				ExecRoot:        er,
   165  				OutputFilePaths: []string{"test"},
   166  			},
   167  		},
   168  		{
   169  			name:          "Clang link command with -L flag",
   170  			command:       []string{"clang++", "-fuse-ld=lld", "-o", "test", "-L", "prebuilts/gcc/linux-x86/lib", "-Lprebuilts/gcc/linux-x86/lib2", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "@test.rsp"},
   171  			existingFiles: map[string][]byte{"test.rsp": []byte("test.o")},
   172  			want: &flags.CommandFlags{
   173  				ExecutablePath: "clang++",
   174  				Flags: []*flags.Flag{
   175  					&flags.Flag{Value: "-fuse-ld=lld"},
   176  					&flags.Flag{Value: "-L"},
   177  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib"},
   178  					&flags.Flag{Value: "-Lprebuilts/gcc/linux-x86/lib2"},
   179  					&flags.Flag{Value: "--sysroot"},
   180  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
   181  					&flags.Flag{Value: "@test.rsp"},
   182  				},
   183  				Dependencies: []string{
   184  					"prebuilts/gcc/linux-x86/lib",
   185  					"prebuilts/gcc/linux-x86/lib2",
   186  					"prebuilts/gcc/linux-x86/bin/",
   187  					"test.rsp",
   188  					"test.o",
   189  				},
   190  				ExecRoot:        er,
   191  				OutputFilePaths: []string{"test"},
   192  			},
   193  		},
   194  		{
   195  			name:          "Clang link command with files in invocation - added as dependency",
   196  			command:       []string{"clang++", "-fuse-ld=lld", "-o", "test", "-L", "prebuilts/gcc/linux-x86/lib", "prebuilts/gcc/linux-x86/lib2.so", "--sysroot", "prebuilts/gcc/linux-x86/bin/", "@test.rsp"},
   197  			existingFiles: map[string][]byte{"test.rsp": []byte("test.o")},
   198  			want: &flags.CommandFlags{
   199  				ExecutablePath: "clang++",
   200  				Flags: []*flags.Flag{
   201  					&flags.Flag{Value: "-fuse-ld=lld"},
   202  					&flags.Flag{Value: "-L"},
   203  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib"},
   204  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib2.so"},
   205  					&flags.Flag{Value: "--sysroot"},
   206  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/bin/"},
   207  					&flags.Flag{Value: "@test.rsp"},
   208  				},
   209  				Dependencies: []string{
   210  					"prebuilts/gcc/linux-x86/lib",
   211  					"prebuilts/gcc/linux-x86/lib2.so",
   212  					"prebuilts/gcc/linux-x86/bin/",
   213  					"test.rsp",
   214  					"test.o",
   215  				},
   216  				ExecRoot:        er,
   217  				OutputFilePaths: []string{"test"},
   218  			},
   219  		},
   220  		{
   221  			name:    "Clang link command with -Wl,--out-implib argument",
   222  			command: []string{"clang++", "-fuse-ld=lld", "-o", "test", "-L", "prebuilts/gcc/linux-x86/lib", "prebuilts/gcc/linux-x86/lib2.so", "-Wl,--out-implib=bar.dll"},
   223  			want: &flags.CommandFlags{
   224  				ExecutablePath: "clang++",
   225  				Flags: []*flags.Flag{
   226  					&flags.Flag{Value: "-fuse-ld=lld"},
   227  					&flags.Flag{Value: "-L"},
   228  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib"},
   229  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib2.so"},
   230  					&flags.Flag{Value: "-Wl,--out-implib=bar.dll"},
   231  				},
   232  				Dependencies: []string{
   233  					"prebuilts/gcc/linux-x86/lib",
   234  					"prebuilts/gcc/linux-x86/lib2.so",
   235  				},
   236  				ExecRoot:        er,
   237  				OutputFilePaths: []string{"test", "bar.dll"},
   238  			},
   239  		},
   240  		{
   241  			name: "Clang link command with linker flags",
   242  			command: []string{"clang++", "-fuse-ld=lld", "-o", "test", "-L", "prebuilts/gcc/linux-x86/lib", "prebuilts/gcc/linux-x86/lib2.so",
   243  				"-Wl,--version-script=version_script",
   244  				"-Wl,--symbol-ordering-file=symbol_ordering_file",
   245  				"-Wl,--dynamic-list=dynamic_list",
   246  				"-Wl,-T=commandfile",
   247  				"-Wl,--retain-symbols-file=retain_symbols_file",
   248  				"-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds",
   249  			},
   250  			want: &flags.CommandFlags{
   251  				ExecutablePath: "clang++",
   252  				Flags: []*flags.Flag{
   253  					&flags.Flag{Value: "-fuse-ld=lld"},
   254  					&flags.Flag{Value: "-L"},
   255  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib"},
   256  					&flags.Flag{Value: "prebuilts/gcc/linux-x86/lib2.so"},
   257  					&flags.Flag{Value: "-Wl,--version-script=version_script"},
   258  					&flags.Flag{Value: "-Wl,--symbol-ordering-file=symbol_ordering_file"},
   259  					&flags.Flag{Value: "-Wl,--dynamic-list=dynamic_list"},
   260  					&flags.Flag{Value: "-Wl,-T=commandfile"},
   261  					&flags.Flag{Value: "-Wl,--retain-symbols-file=retain_symbols_file"},
   262  					&flags.Flag{Value: "-Wl,--script,external/cronet/base/android/library_loader/anchor_functions.lds"},
   263  				},
   264  				Dependencies: []string{
   265  					"prebuilts/gcc/linux-x86/lib",
   266  					"prebuilts/gcc/linux-x86/lib2.so",
   267  					"version_script",
   268  					"symbol_ordering_file",
   269  					"dynamic_list",
   270  					"commandfile",
   271  					"retain_symbols_file",
   272  					"external/cronet/base/android/library_loader/anchor_functions.lds",
   273  				},
   274  				ExecRoot:        er,
   275  				OutputFilePaths: []string{"test"},
   276  			},
   277  		},
   278  	}
   279  	for _, test := range test {
   280  		t.Run(test.name, func(t *testing.T) {
   281  			execroot.AddFilesWithContent(t, er, test.existingFiles)
   282  			defer func() {
   283  				for f := range test.existingFiles {
   284  					if err := os.Remove(filepath.Join(er, f)); err != nil {
   285  						// Error because they can affect other tests.
   286  						t.Errorf("Failed to clean test file: %v", err)
   287  					}
   288  				}
   289  			}()
   290  
   291  			p := &Preprocessor{
   292  				&inputprocessor.BasePreprocessor{
   293  					Options: inputprocessor.Options{
   294  						Cmd:      test.command,
   295  						ExecRoot: er,
   296  					},
   297  				},
   298  				true,
   299  			}
   300  			if err := p.ParseFlags(); err != nil {
   301  				t.Errorf("ParseFlags() returned error: %v", err)
   302  			}
   303  			if diff := cmp.Diff(test.want, p.Flags, cmpopts.IgnoreUnexported(flags.Flag{})); diff != "" {
   304  				t.Errorf("ParseFlags() returned diff, (-want +got): %s", diff)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  // TestArchiveDeep scans the test archive and verifies the contents are returned.
   311  func TestArchiveDeep(t *testing.T) {
   312  	wantContents := []string{
   313  		"foo.o",
   314  		"bar.o",
   315  		"baz.o",
   316  	}
   317  
   318  	f, err := bazel.Runfile("testdata/testarchive.a")
   319  	if err != nil {
   320  		t.Fatalf("TestArchiveDeep: Failed to find archive %v", err)
   321  	}
   322  
   323  	deps, err := readArchive(f, "")
   324  	if err != nil {
   325  		t.Fatalf("TestArchiveDeep: Failed to read archive %v", err)
   326  	}
   327  
   328  	if diff := cmp.Diff(wantContents, deps); diff != "" {
   329  		t.Errorf("TestArchiveDeep: returned diff, (-want +got): %s", diff)
   330  	}
   331  }
   332  
   333  // TestArchiveDeepFailure ensures an error is returned if the archive could not be read.
   334  func TestArchiveDeepFailure(t *testing.T) {
   335  
   336  	f := "testdata/missingarchive.a"
   337  
   338  	_, err := readArchive(f, "")
   339  	if err == nil {
   340  		t.Errorf("TestArchiveDeepFailure: readArchive successful; expected failure")
   341  	}
   342  }