tools: Add the 'cts update' sub-command

Updates the expectations file with new results.

Bug: dawn:1342
Change-Id: I05cab48eb1cf0f9ab7e652a1b7c3da361c45b0b1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/88442
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/tools/src/cmd/cts/main.go b/tools/src/cmd/cts/main.go
index af8660d..0aefd29 100644
--- a/tools/src/cmd/cts/main.go
+++ b/tools/src/cmd/cts/main.go
@@ -32,6 +32,7 @@
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/merge"
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/results"
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/time"
+	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/update"
 )
 
 func main() {
diff --git a/tools/src/cmd/cts/update/update.go b/tools/src/cmd/cts/update/update.go
new file mode 100644
index 0000000..4301393
--- /dev/null
+++ b/tools/src/cmd/cts/update/update.go
@@ -0,0 +1,87 @@
+// Copyright 2022 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package update
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"dawn.googlesource.com/dawn/tools/src/cmd/cts/common"
+	"dawn.googlesource.com/dawn/tools/src/cts/expectations"
+	"go.chromium.org/luci/auth/client/authcli"
+)
+
+func init() {
+	common.Register(&cmd{})
+}
+
+type cmd struct {
+	flags struct {
+		results      common.ResultSource
+		expectations string
+		auth         authcli.Flags
+	}
+}
+
+func (cmd) Name() string {
+	return "update"
+}
+
+func (cmd) Desc() string {
+	return "updates a CTS expectations file"
+}
+
+func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
+	defaultExpectations := common.DefaultExpectationsPath()
+	c.flags.results.RegisterFlags(cfg)
+	c.flags.auth.Register(flag.CommandLine, common.DefaultAuthOptions())
+	flag.StringVar(&c.flags.expectations, "expectations", defaultExpectations, "path to CTS expectations file to update")
+	return nil, nil
+}
+
+func (c *cmd) Run(ctx context.Context, cfg common.Config) error {
+	// Validate command line arguments
+	auth, err := c.flags.auth.Options()
+	if err != nil {
+		return fmt.Errorf("failed to obtain authentication options: %w", err)
+	}
+
+	// Fetch the results
+	results, err := c.flags.results.GetResults(ctx, cfg, auth)
+	if err != nil {
+		return err
+	}
+
+	// Load the expectations file
+	ex, err := expectations.Load(c.flags.expectations)
+	if err != nil {
+		return err
+	}
+
+	// Update the expectations file with the results
+	msgs, err := ex.Update(results)
+	if err != nil {
+		return err
+	}
+
+	// Print any diagnostics
+	for _, msg := range msgs {
+		fmt.Printf("%v:%v %v\n", c.flags.expectations, msg.Line, msg.Message)
+	}
+
+	// Save the updated expectations file
+	return ex.Save(c.flags.expectations)
+}