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 }