dawn_node: make run_cts accept "--flag=<flag>=<value>" argument

These flags are forwarded to the cts cmdline/server to configure dawn.node.
I also documented this in the README, along with examples.

Bug: dawn:1163
Change-Id: I6e213f1bd64700564c2e54dcd4edcf219ef2e829
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68060
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/dawn_node/README.md b/src/dawn_node/README.md
index 24aaf80..41496c1 100644
--- a/src/dawn_node/README.md
+++ b/src/dawn_node/README.md
@@ -53,8 +53,25 @@
 
 If this fails with the error message `TypeError: expander is not a function or its return value is not iterable`, try appending `--build=false` to the start of the `run-cts` command line flags.
 
-To test against SwiftShader instead of the default Vulkan device, prefix `./src/dawn_node/tools/run-cts` with `VK_ICD_FILENAMES=<swiftshader-cmake-build>/Linux/vk_swiftshader_icd.json`
+To test against SwiftShader instead of the default Vulkan device, prefix `./src/dawn_node/tools/run-cts` with `VK_ICD_FILENAMES=<swiftshader-cmake-build>/Linux/vk_swiftshader_icd.json` and append `--flag=dawn-backend=vulkan` to the start of run-cts command line flags. For example:
 
+```sh
+VK_ICD_FILENAMES=<swiftshader-cmake-build>/Linux/vk_swiftshader_icd.json ./src/dawn_node/tools/run-cts --cts=<path-to-webgpu-cts> --dawn-node=<path-to-dawn.node> --flag=dawn-backend=vulkan [WebGPU CTS query]
+```
+
+The `--flag` parameter must be passed in multiple times, once for each flag begin set. Here are some common arguments:
+* `dawn-backend=<null|webgpu|d3d11|d3d12|metal|vulkan|opengl|opengles>`
+* `dlldir=<path>` - used to add an extra DLL search path on Windows, primarily to load the right d3dcompiler_47.dll
+* `enable-dawn-features=<features>` - enable [Dawn toggles](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/dawn_native/Toggles.cpp), e.g. `dump_shaders`
+* `disable-dawn-features=<features>` - disable [Dawn toggles](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/dawn_native/Toggles.cpp)
+
+For example, on Windows, to use the d3dcompiler_47.dll from a Chromium checkout, and to dump shader output, we could run the following using Git Bash:
+
+```sh
+./src/dawn_node/tools/run-cts --j=0 --dawn-node=/c/src/dawn/build/Debug/dawn.node --cts=/c/src/gpuweb-cts --flag=dlldir="C:\src\chromium\src\out\Release" --flag=enable-dawn-features=dump_shaders 'webgpu:shader,execution,builtin,abs:integer_builtin_functions,abs_unsigned:storageClass="storage";storageMode="read_write";containerType="vector";isAtomic=false;baseType="u32";type="vec2%3Cu32%3E"'
+```
+
+Note that we pass `--j=0` above so that all output, including the dumped shader, is written to stdout.
 ## Known issues
 
 - Many WebGPU CTS tests are currently known to fail
diff --git a/src/dawn_node/tools/src/cmd/run-cts/main.go b/src/dawn_node/tools/src/cmd/run-cts/main.go
index 1570d9f..c61c08d 100644
--- a/src/dawn_node/tools/src/cmd/run-cts/main.go
+++ b/src/dawn_node/tools/src/cmd/run-cts/main.go
@@ -63,6 +63,19 @@
 var colors bool
 var stdout io.Writer
 
+type dawnNodeFlags []string
+
+func (f *dawnNodeFlags) String() string {
+	return fmt.Sprint(strings.Join(*f, ""))
+}
+
+func (f *dawnNodeFlags) Set(value string) error {
+	// Multiple flags must be passed in indivually:
+	// -flag=a=b -dawn_node_flag=c=d
+	*f = append(*f, value)
+	return nil
+}
+
 func run() error {
 	colors = os.Getenv("TERM") != "dumb" ||
 		isatty.IsTerminal(os.Stdout.Fd()) ||
@@ -76,6 +89,7 @@
 	var dawnNode, cts, node, npx, logFilename string
 	var verbose, isolated, build bool
 	var numRunners int
+	var flags dawnNodeFlags
 	flag.StringVar(&dawnNode, "dawn-node", "", "path to dawn.node module")
 	flag.StringVar(&cts, "cts", "", "root directory of WebGPU CTS")
 	flag.StringVar(&node, "node", "", "path to node executable")
@@ -86,6 +100,7 @@
 	flag.BoolVar(&colors, "colors", colors, "enable / disable colors")
 	flag.IntVar(&numRunners, "j", runtime.NumCPU()/2, "number of concurrent runners. 0 runs serially")
 	flag.StringVar(&logFilename, "log", "", "path to log file of tests run and result")
+	flag.Var(&flags, "flag", "flag to pass to dawn-node as flag=value. multiple flags must be passed in individually")
 	flag.Parse()
 
 	if colors {
@@ -148,6 +163,7 @@
 		npx:        npx,
 		dawnNode:   dawnNode,
 		cts:        cts,
+		flags:      flags,
 		evalScript: func(main string) string {
 			return fmt.Sprintf(`require('./src/common/tools/setup-ts-in-node.js');require('./src/common/runtime/%v.ts');`, main)
 		},
@@ -272,6 +288,7 @@
 	numRunners               int
 	verbose                  bool
 	node, npx, dawnNode, cts string
+	flags                    dawnNodeFlags
 	evalScript               func(string) string
 	testcases                []string
 	log                      logger
@@ -432,7 +449,7 @@
 
 	stopServer := func() {}
 	startServer := func() error {
-		cmd := exec.Command(r.node,
+		args := []string{
 			"-e", r.evalScript("server"), // Evaluate 'eval'.
 			"--",
 			// src/common/runtime/helper/sys.ts expects 'node file.js <args>'
@@ -440,7 +457,13 @@
 			// start at 1, so just inject a dummy argument.
 			"dummy-arg",
 			// Actual arguments begin here
-			"--gpu-provider", r.dawnNode)
+			"--gpu-provider", r.dawnNode,
+		}
+		for _, f := range r.flags {
+			args = append(args, "--gpu-provider-flag", f)
+		}
+
+		cmd := exec.Command(r.node, args...)
 
 		serverLog := &bytes.Buffer{}
 
@@ -678,7 +701,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
 	defer cancel()
 
-	args := append([]string{
+	args := []string{
 		"-e", r.evalScript("cmdline"), // Evaluate 'eval'.
 		"--",
 		// src/common/runtime/helper/sys.ts expects 'node file.js <args>'
@@ -688,7 +711,11 @@
 		// Actual arguments begin here
 		"--gpu-provider", r.dawnNode,
 		"--verbose",
-	}, query)
+	}
+	for _, f := range r.flags {
+		args = append(args, "--gpu-provider-flag", f)
+	}
+	args = append(args, query)
 
 	cmd := exec.CommandContext(ctx, r.node, args...)
 	cmd.Dir = r.cts