Update tnull to use new Invocation proto

This is a partial revert of https://crrev.com/c/2488843, since we went
from multi-request to single request and now back to multi-request.

BUG=chromium:1130070,chromium:1127071
TEST=local runs

Change-Id: I9ba45fdf2493a94926041996242c1f73b69bbc79
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/infra/tnull/+/2511119
Tested-by: Sean Abraham <seanabraham@chromium.org>
Auto-Submit: Sean Abraham <seanabraham@chromium.org>
Reviewed-by: Jared Loucks <jaredloucks@google.com>
Commit-Queue: Jared Loucks <jaredloucks@google.com>
diff --git a/src/tnull/cmd/execute.go b/src/tnull/cmd/execute.go
index fef8dfb..8b0a8db 100644
--- a/src/tnull/cmd/execute.go
+++ b/src/tnull/cmd/execute.go
@@ -58,8 +58,8 @@
 	if err := readBinaryProto(r.invocationPath, &inv); err != nil {
 		return errors.Annotate(err, "reading in the Invocation").Err()
 	}
-	if inv.GetName() == "" {
-		return errors.Reason("no name in invocation").Err()
+	if inv.GetRequests() == nil || len(inv.GetRequests()) == 0 {
+		return errors.Reason("no requests in invocation").Err()
 	}
 
 	lookup, err := extractTestsFromSpecification()
@@ -68,15 +68,18 @@
 	}
 
 	conf := inv.ProgressSinkClientConfig
+	reqs := inv.Requests
 	errs := errors.MultiError{}
 	r.debugDest = a.GetErr()
 
-	fmt.Fprintf(a.GetOut(), "executing test: %s\n", inv.GetTest())
-	if steps, present := lookup.Lookup[inv.GetTest()]; !present {
-		errs = append(errs, errors.Reason("no test with the name %s", inv.GetName()).Err())
-	} else {
-		steps.Setup.Config = conf
-		r.Execute(inv.GetName(), steps)
+	for _, req := range reqs {
+		fmt.Fprintf(a.GetOut(), "req %s: executing test %s\n", req.GetName(), req.GetTest())
+		if steps, present := lookup.Lookup[req.GetTest()]; !present {
+			errs = append(errs, errors.Reason("no test with the name %s", req.GetTest()).Err())
+		} else {
+			steps.Setup.Config = conf
+			r.Execute(req.GetTest(), steps)
+		}
 	}
 	if len(errs) == 0 {
 		return nil
diff --git a/src/tnull/cmd/generate.go b/src/tnull/cmd/generate.go
index 86d7dae..a74be0f 100644
--- a/src/tnull/cmd/generate.go
+++ b/src/tnull/cmd/generate.go
@@ -42,7 +42,7 @@
 				`Optional: path that contains a JSON list of test names to include in invocation.
 				Each name should look like 'dummy-pass' or look
 				like "remoteTestDrivers/tnull/tests/dummy-pass"`)
-			g.Flags.StringVar(&g.destBinaryProto, "output_dir", "", "Directory to contain binaryproto rtd.Invocation objects")
+			g.Flags.StringVar(&g.destBinaryProto, "output", "", "File to contain binaryproto rtd.Invocation objects")
 			g.Flags.BoolVar(&g.alsoOutputJson, "also_output_json", true, "Whether to create output JSON rtd.Invocation files in addition to the binaryprotos, in the same directory")
 			return g
 		},
@@ -75,7 +75,7 @@
 		return err
 	}
 
-	err = WriteInvocations(ctx, validMap, g.destBinaryProto, g.alsoOutputJson)
+	err = WriteInvocation(ctx, validMap, g.destBinaryProto, g.alsoOutputJson)
 	if err != nil {
 		return err
 	}
@@ -98,44 +98,49 @@
 	return names, nil
 }
 
-// WriteInvocations writes binary-encoded Invocation protos to destDir.
-func WriteInvocations(ctx context.Context, tests map[string]*tnProto.Steps, destDir string, alsoOutputJson bool) error {
-	dir := filepath.Dir(destDir)
+// WriteInvocation writes a binary-encoded Invocation proto to destFile.
+func WriteInvocation(ctx context.Context, tests map[string]*tnProto.Steps, destFile string, alsoOutputJson bool) error {
+	var reqs []*rtd.Request
+	for _, name := range mapKeysToSortedList(tests) {
+		req := rtd.Request{
+			Name: "request_" + uri.Base(name),
+			Test: name,
+		}
+		reqs = append(reqs, &req)
+	}
+	Inv := rtd.Invocation{
+		Requests: reqs,
+	}
+	dir := filepath.Dir(destFile)
 	// Create the directory if it doesn't exist.
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return errors.Annotate(err, "write invocation pb").Err()
 	}
-	for _, name := range mapKeysToSortedList(tests) {
-		inv := rtd.Invocation{
-			Name: "request_" + uri.Base(name),
-			Test: name,
-		}
-		destFile := filepath.Join(dir, inv.Name+".binaryproto")
-		{
-			protoBytes, err := proto.Marshal(&inv)
-			if err != nil {
-				return errors.Annotate(err, "write Invocation binaryproto").Err()
-			}
-			if err = ioutil.WriteFile(destFile, protoBytes, 0644); err != nil {
-				return errors.Annotate(err, "write Invocation binaryproto").Err()
-			}
-			logging.Infof(ctx, "Wrote binaryproto Invocation to %v", destFile)
-		}
 
-		if alsoOutputJson {
-			jsonFile := strings.TrimSuffix(destFile, ".binaryproto") + ".json"
-			w, err := os.Create(jsonFile)
-			if err != nil {
-				return errors.Annotate(err, "write Invocation pb").Err()
-			}
-			defer w.Close()
-
-			marshaler := jsonpb.Marshaler{Indent: "  "}
-			if err := marshaler.Marshal(w, &inv); err != nil {
-				return errors.Annotate(err, "write JSON pb").Err()
-			}
-			logging.Infof(ctx, "Wrote JSON Invocation to %v", jsonFile)
+	{
+		protoBytes, err := proto.Marshal(&Inv)
+		if err != nil {
+			return errors.Annotate(err, "write Invocation binaryproto").Err()
 		}
+		if err = ioutil.WriteFile(destFile, protoBytes, 0644); err != nil {
+			return errors.Annotate(err, "write Invocation binaryproto").Err()
+		}
+		logging.Infof(ctx, "Wrote binaryproto Invocation to %v", destFile)
+	}
+
+	if alsoOutputJson {
+		jsonFile := strings.TrimSuffix(destFile, ".binaryproto") + ".json"
+		w, err := os.Create(jsonFile)
+		if err != nil {
+			return errors.Annotate(err, "write Invocation pb").Err()
+		}
+		defer w.Close()
+
+		marshaler := jsonpb.Marshaler{Indent: "  "}
+		if err := marshaler.Marshal(w, &Inv); err != nil {
+			return errors.Annotate(err, "write JSON pb").Err()
+		}
+		logging.Infof(ctx, "Wrote JSON Invocation to %v", jsonFile)
 	}
 	return nil
 }
diff --git a/src/tnull/driver/driver.go b/src/tnull/driver/driver.go
index 48c063d..a942567 100644
--- a/src/tnull/driver/driver.go
+++ b/src/tnull/driver/driver.go
@@ -94,7 +94,7 @@
 }
 
 // Archive sends all the stored mock artifacts to the RPC service, in parallel
-func (d *Driver) Archive(invocationName string) error {
+func (d *Driver) Archive(requestName string) error {
 	return parallel.WorkPool(0, func(fchan chan<- func() error) {
 		for _, a := range d.artifacts {
 			a := a
@@ -105,9 +105,9 @@
 				}
 				ioutil.WriteFile(tmp.Name(), a.FileBytes, os.ModePerm)
 				req := &rtd.ArchiveArtifactRequest{
-					Name:       a.Name,
-					Invocation: invocationName,
-					LocalPath:  tmp.Name(),
+					Name:      a.Name,
+					Request:   requestName,
+					LocalPath: tmp.Name(),
 				}
 				resp := d.client.archiveArtifact(req)
 				d.client.debugLog(req, resp)
@@ -118,7 +118,7 @@
 }
 
 // Log reports all the stored mock logs to the RPC service, in parallel
-func (d *Driver) Log(invocationName string) error {
+func (d *Driver) Log(requestName string) error {
 	return parallel.WorkPool(0, func(fchan chan<- func() error) {
 		for _, l := range d.logs {
 			l := l
@@ -126,9 +126,9 @@
 				m := m
 				fchan <- func() error {
 					req := &rtd.ReportLogRequest{
-						Name:       l.Name,
-						Invocation: invocationName,
-						Data:       []byte(m),
+						Name:    l.Name,
+						Request: requestName,
+						Data:    []byte(m),
 					}
 					resp := d.client.reportLog(req)
 					d.client.debugLog(req, resp)
@@ -140,12 +140,12 @@
 }
 
 // Result reports the already-stored result to the RPC service
-func (d *Driver) Result(invocationName string) error {
+func (d *Driver) Result(requestName string) error {
 	var wasFirstTime bool
 	d.emitResult.Do(func() {
 		req := &rtd.ReportResultRequest{
-			Invocation: invocationName,
-			Result:     d.result,
+			Request: requestName,
+			Result:  d.result,
 		}
 		resp := d.client.reportResult(req)
 		d.client.debugLog(req, resp)