1// Copyright 2023 The Android Open Source Project 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 15package soong_wayland_protocol_codegen 16 17import ( 18 "os" 19 "regexp" 20 "testing" 21 22 "android/soong/android" 23) 24 25func TestMain(m *testing.M) { 26 os.Exit(m.Run()) 27} 28 29var prepareForCodeGenTest = android.GroupFixturePreparers( 30 android.PrepareForTestWithArchMutator, 31 android.PrepareForTestWithDefaults, 32 android.PrepareForTestWithFilegroup, 33 android.GroupFixturePreparers( 34 android.FixtureRegisterWithContext(registerCodeGenBuildComponents), 35 ), 36 android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { 37 android.RegisterPrebuiltMutators(ctx) 38 ctx.RegisterModuleType("fake_android_host_tool", fakeAndroidHostToolFactory) 39 }), 40 android.FixtureMergeMockFs(android.MockFS{ 41 "android_host_tool": nil, 42 "tool_src_file": nil, 43 "tool_src_file_1": nil, 44 "tool_src_file_2": nil, 45 "src_file": nil, 46 "src_file_1": nil, 47 "src_file_2": nil, 48 }), 49) 50 51func testCodeGenBp() string { 52 return ` 53 fake_android_host_tool { 54 name: "host_tool", 55 } 56 57 filegroup { 58 name: "tool_single_source_file_filegroup", 59 srcs: [ 60 "tool_src_file", 61 ], 62 } 63 64 filegroup { 65 name: "tool_multi_source_files_filegroup", 66 srcs: [ 67 "tool_src_file_1", 68 "tool_src_file_2", 69 ], 70 } 71 72 filegroup { 73 name: "single_source_filegroup", 74 srcs: [ 75 "src_file", 76 ], 77 } 78 79 filegroup { 80 name: "multi_source_filegroup", 81 srcs: [ 82 "src_file_1", 83 "src_file_2", 84 ], 85 } 86 87 filegroup { 88 name: "empty_filegroup", 89 } 90 ` 91} 92 93func TestWaylandCodeGen(t *testing.T) { 94 testcases := []struct { 95 name string 96 prop string 97 98 err string 99 cmds []string 100 files []string 101 }{ 102 { 103 name: "single_source_with_host_tool", 104 prop: ` 105 tools: ["host_tool"], 106 srcs: ["src_file"], 107 output: "prefix_$(in)_suffix", 108 cmd: "$(location host_tool) gen < $(in) > $(out)", 109 `, 110 cmds: []string{ 111 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'", 112 }, 113 files: []string{ 114 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix", 115 }, 116 }, 117 { 118 name: "multi_source_with_host_tool", 119 prop: ` 120 tools: ["host_tool"], 121 srcs: ["src_file_1", "src_file_2"], 122 output: "prefix_$(in)_suffix", 123 cmd: "$(location host_tool) gen < $(in) > $(out)", 124 `, 125 cmds: []string{ 126 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'", 127 }, 128 files: []string{ 129 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix", 130 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix", 131 }, 132 }, 133 { 134 name: "single_source_filegroup_with_host_tool", 135 prop: ` 136 tools: ["host_tool"], 137 srcs: [":single_source_filegroup"], 138 output: "prefix_$(in)_suffix", 139 cmd: "$(location host_tool) gen < $(in) > $(out)", 140 `, 141 cmds: []string{ 142 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'", 143 }, 144 files: []string{ 145 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix", 146 }, 147 }, 148 { 149 name: "multi_source_filegroup_with_host_tool", 150 prop: ` 151 tools: ["host_tool"], 152 srcs: [":multi_source_filegroup"], 153 output: "prefix_$(in)_suffix", 154 cmd: "$(location host_tool) gen < $(in) > $(out)", 155 `, 156 cmds: []string{ 157 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'", 158 }, 159 files: []string{ 160 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix", 161 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix", 162 }, 163 }, 164 { 165 name: "single_source_with_single_tool_file", 166 prop: ` 167 tool_files: ["tool_src_file"], 168 srcs: ["src_file"], 169 output: "prefix_$(in)_suffix", 170 cmd: "$(location tool_src_file) gen < $(in) > $(out)", 171 `, 172 cmds: []string{ 173 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'", 174 }, 175 files: []string{ 176 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix", 177 }, 178 }, 179 { 180 name: "multi_source_with_single_tool_file", 181 prop: ` 182 tool_files: ["tool_src_file"], 183 srcs: ["src_file_1", "src_file_2"], 184 output: "prefix_$(in)_suffix", 185 cmd: "$(location tool_src_file) gen < $(in) > $(out)", 186 `, 187 cmds: []string{ 188 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'", 189 }, 190 files: []string{ 191 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix", 192 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix", 193 }, 194 }, 195 { 196 name: "single_source_filegroup_with_single_tool_file", 197 prop: ` 198 tool_files: ["tool_src_file"], 199 srcs: [":single_source_filegroup"], 200 output: "prefix_$(in)_suffix", 201 cmd: "$(location tool_src_file) gen < $(in) > $(out)", 202 `, 203 cmds: []string{ 204 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'", 205 }, 206 files: []string{ 207 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix", 208 }, 209 }, 210 { 211 name: "multi_source_filegroup_with_single_tool_file", 212 prop: ` 213 tool_files: ["tool_src_file"], 214 srcs: [":multi_source_filegroup"], 215 output: "prefix_$(in)_suffix", 216 cmd: "$(location tool_src_file) gen < $(in) > $(out)", 217 `, 218 cmds: []string{ 219 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_1 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_1_suffix' && bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file gen < src_file_2 > __SBOX_SANDBOX_DIR__/out/prefix_src_file_2_suffix'", 220 }, 221 files: []string{ 222 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_1_suffix", 223 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_2_suffix", 224 }, 225 }, 226 { 227 name: "multiple_tool_files", 228 prop: ` 229 tool_files: ["tool_src_file_1", "tool_src_file_2"], 230 srcs: ["src_file"], 231 output: "prefix_$(in)_suffix", 232 cmd: "$(location tool_src_file_1) $(location tool_src_file_2) gen < $(in) > $(out)", 233 `, 234 cmds: []string{ 235 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/tool_src_file_1 __SBOX_SANDBOX_DIR__/tools/src/tool_src_file_2 gen < src_file > __SBOX_SANDBOX_DIR__/out/prefix_src_file_suffix'", 236 }, 237 files: []string{ 238 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/prefix_src_file_suffix", 239 }, 240 }, 241 { 242 name: "output_template_explicit_base_only", 243 prop: ` 244 tools: ["host_tool"], 245 srcs: ["txt/a/file.txt"], 246 output: "$(in:base)", 247 cmd: "$(location host_tool) gen < $(in) > $(out)", 248 `, 249 cmds: []string{ 250 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/file'", 251 }, 252 files: []string{ 253 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/file", 254 }, 255 }, 256 { 257 name: "output_template_explicit_base_and_ext", 258 prop: ` 259 tools: ["host_tool"], 260 srcs: ["txt/a/file.txt"], 261 output: "$(in:base.ext)", 262 cmd: "$(location host_tool) gen < $(in) > $(out)", 263 `, 264 cmds: []string{ 265 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/file.txt'", 266 }, 267 files: []string{ 268 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/file.txt", 269 }, 270 }, 271 { 272 name: "output_template_explicit_path_and_base", 273 prop: ` 274 tools: ["host_tool"], 275 srcs: ["txt/a/file.txt"], 276 output: "$(in:path/base)", 277 cmd: "$(location host_tool) gen < $(in) > $(out)", 278 `, 279 cmds: []string{ 280 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/txt/a/file'", 281 }, 282 files: []string{ 283 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/txt/a/file", 284 }, 285 }, 286 { 287 name: "output_template_explicit_path_and_base_and_ext", 288 prop: ` 289 tools: ["host_tool"], 290 srcs: ["txt/a/file.txt"], 291 output: "$(in:path/base.ext)", 292 cmd: "$(location host_tool) gen < $(in) > $(out)", 293 `, 294 cmds: []string{ 295 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < txt/a/file.txt > __SBOX_SANDBOX_DIR__/out/txt/a/file.txt'", 296 }, 297 files: []string{ 298 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/txt/a/file.txt", 299 }, 300 }, 301 { 302 name: "single_source_file_does_not_need_distinct_outputs", 303 prop: ` 304 tools: ["host_tool"], 305 srcs: ["src_file"], 306 output: "output", 307 cmd: "$(location host_tool) gen < $(in) > $(out)", 308 `, 309 cmds: []string{ 310 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/output'", 311 }, 312 files: []string{ 313 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/output", 314 }, 315 }, 316 { 317 name: "legacy_prefix_suffix", 318 prop: ` 319 tools: ["host_tool"], 320 srcs: ["src_file"], 321 prefix: "legacy_prefix_", 322 suffix: "_legacy_suffix", 323 cmd: "$(location host_tool) gen < $(in) > $(out)", 324 `, 325 cmds: []string{ 326 "bash -c '__SBOX_SANDBOX_DIR__/tools/src/out/host_tool gen < src_file > __SBOX_SANDBOX_DIR__/out/legacy_prefix_src_file_legacy_suffix'", 327 }, 328 files: []string{ 329 "out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/legacy_prefix_src_file_legacy_suffix", 330 }, 331 }, 332 { 333 name: "error_if_no_sources", 334 prop: ` 335 tools: ["host_tool"], 336 cmd: "$(location host_tool) gen < $(in) > $(out)", 337 `, 338 err: "must have at least one source file", 339 }, 340 { 341 name: "error_if_no_filegroup_sources", 342 prop: ` 343 tools: ["host_tool"], 344 srcs: [":empty_filegroup"], 345 cmd: "$(location host_tool) gen < $(in) > $(out)", 346 `, 347 err: "must have at least one source file", 348 }, 349 { 350 name: "error_if_in_outputs_are_not_distinct", 351 prop: ` 352 tools: ["host_tool"], 353 tool_files: ["tool_src_file"], 354 srcs: ["src_file_1", "src_file_2"], 355 output: "not_unique", 356 cmd: "$(location)" 357 `, 358 err: "Android.bp:39:2: module \"codegen\": generation conflict: both 'src_file_1' and 'src_file_2' generate 'not_unique'", 359 }, 360 { 361 name: "error_if_output_expansion_fails", 362 prop: ` 363 tools: ["host_tool"], 364 tool_files: ["tool_src_file"], 365 srcs: ["src_file"], 366 output: "prefix_$(bad)_suffix", 367 cmd: "$(location)" 368 `, 369 err: "Android.bp:45:11: module \"codegen\": output: unknown variable '$(bad)'", 370 }, 371 { 372 name: "error_if_cmd_expansion_fails", 373 prop: ` 374 tools: ["host_tool"], 375 tool_files: ["tool_src_file"], 376 srcs: ["src_file"], 377 output: "prefix_$(in)_suffix", 378 cmd: "$(location bad_name)" 379 `, 380 err: "Android.bp:46:8: module \"codegen\": cmd: unknown location label \"bad_name\"", 381 }, 382 } 383 384 for _, test := range testcases { 385 t.Run(test.name, func(t *testing.T) { 386 bp := "wayland_protocol_codegen {\n" 387 bp += `name: "codegen",` + "\n" 388 bp += test.prop 389 bp += "}\n" 390 391 var expectedErrors []string 392 if test.err != "" { 393 expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err)) 394 } 395 396 result := prepareForCodeGenTest. 397 ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). 398 RunTestWithBp(t, testCodeGenBp()+bp) 399 400 if expectedErrors != nil { 401 return 402 } 403 404 gen := result.Module("codegen", "").(*Module) 405 android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands) 406 android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles) 407 }) 408 } 409} 410 411func TestGenruleWithBazel(t *testing.T) { 412 bp := ` 413 wayland_protocol_codegen { 414 name: "mixed_codegen", 415 srcs: ["src_file"], 416 bazel_module: { label: "//example:bazel_codegen" }, 417 } 418 ` 419 420 result := android.GroupFixturePreparers( 421 prepareForCodeGenTest, android.FixtureModifyConfig(func(config android.Config) { 422 config.BazelContext = android.MockBazelContext{ 423 OutputBaseDir: "outputbase", 424 LabelToOutputFiles: map[string][]string{ 425 "//example:bazel_codegen": {"bazelone.txt", "bazeltwo.txt"}}} 426 })).RunTestWithBp(t, testCodeGenBp()+bp) 427 428 gen := result.Module("mixed_codegen", "").(*Module) 429 430 expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt", 431 "outputbase/execroot/__main__/bazeltwo.txt"} 432 android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings()) 433 android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings()) 434} 435 436func TestDefaults(t *testing.T) { 437 bp := ` 438 wayland_protocol_codegen_defaults { 439 name: "gen_defaults1", 440 cmd: "cp $(in) $(out)", 441 output: "$(in).h", 442 } 443 444 wayland_protocol_codegen_defaults { 445 name: "gen_defaults2", 446 srcs: ["in1"], 447 } 448 449 wayland_protocol_codegen { 450 name: "codegen", 451 defaults: ["gen_defaults1", "gen_defaults2"], 452 } 453 ` 454 455 result := prepareForCodeGenTest.RunTestWithBp(t, testCodeGenBp()+bp) 456 457 gen := result.Module("codegen", "").(*Module) 458 459 expectedCmd := "bash -c cp in1 __SBOX_SANDBOX_DIR__/out/in1.h" 460 android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0]) 461 462 expectedSrcs := []string{"in1"} 463 android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs) 464 465 expectedFiles := []string{"out/soong/.intermediates/codegen/gen/wayland_protocol_codegen/in1.h"} 466 android.AssertPathsRelativeToTopEquals(t, "files", expectedFiles, gen.outputFiles) 467} 468 469type fakeAndroidHostTool struct { 470 android.ModuleBase 471 outputFile android.Path 472} 473 474func fakeAndroidHostToolFactory() android.Module { 475 module := &fakeAndroidHostTool{} 476 android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) 477 return module 478} 479 480func (t *fakeAndroidHostTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { 481 t.outputFile = android.PathForTesting("out", ctx.ModuleName()) 482} 483 484func (t *fakeAndroidHostTool) HostToolPath() android.OptionalPath { 485 return android.OptionalPathForPath(t.outputFile) 486} 487 488var _ android.HostToolProvider = (*fakeAndroidHostTool)(nil) 489