Add --test to gen_tasks.go, add presubmit check

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2392083002

Review-Url: https://codereview.chromium.org/2392083002
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 8115b49..4798a02 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -183,6 +183,24 @@
         '`%s` failed:\n%s' % (' '.join(cmd), e.output)))
   return results
 
+
+def _GenTasksTest(input_api, output_api):
+  """Run gen_tasks.go test."""
+  results = []
+  if not any(f.LocalPath().startswith('infra')
+             for f in input_api.AffectedFiles()):
+    return results
+
+  gen_tasks = os.path.join('infra', 'bots', 'gen_tasks.go')
+  cmd = ['go', 'run', gen_tasks, '--test']
+  try:
+    subprocess.check_output(cmd)
+  except subprocess.CalledProcessError as e:
+    results.append(output_api.PresubmitError(
+        '`%s` failed:\n%s' % (' '.join(cmd), e.output)))
+  return results
+
+
 def _CheckGNFormatted(input_api, output_api):
   """Make sure any .gn files we're changing have been formatted."""
   results = []
@@ -234,8 +252,10 @@
   results = []
   results.extend(_CommonChecks(input_api, output_api))
   # Run on upload, not commit, since the presubmit bot apparently doesn't have
-  # coverage installed.
+  # coverage or Go installed.
   results.extend(_RecipeSimulationTest(input_api, output_api))
+  results.extend(_GenTasksTest(input_api, output_api))
+
   results.extend(_CheckGNFormatted(input_api, output_api))
   return results
 
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index 50af1b9..0751124 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -11,6 +11,7 @@
 import (
 	"bytes"
 	"encoding/json"
+	"flag"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -62,6 +63,9 @@
 
 	// Path to the infra/bots directory.
 	infrabotsDir = ""
+
+	// Flags.
+	testing = flag.Bool("test", false, "Run in test mode: verify that the output hasn't changed.")
 )
 
 // deriveCompileTaskName returns the name of a compile task based on the given
@@ -492,7 +496,6 @@
 	}
 
 	// Write the tasks.json file.
-	outFile := path.Join(root, specs.TASKS_CFG_FILE)
 	b, err := json.MarshalIndent(cfg, "", "  ")
 	if err != nil {
 		glog.Fatal(err)
@@ -501,8 +504,21 @@
 	// much less readable. Replace the escape characters with the real
 	// character.
 	b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
-	if err := ioutil.WriteFile(outFile, b, os.ModePerm); err != nil {
-		glog.Fatal(err)
+
+	outFile := path.Join(root, specs.TASKS_CFG_FILE)
+	if *testing {
+		// Don't write the file; read it and compare.
+		expect, err := ioutil.ReadFile(outFile)
+		if err != nil {
+			glog.Fatal(err)
+		}
+		if !bytes.Equal(expect, b) {
+			glog.Fatalf("Expected no changes, but changes were found!")
+		}
+	} else {
+		if err := ioutil.WriteFile(outFile, b, os.ModePerm); err != nil {
+			glog.Fatal(err)
+		}
 	}
 }