internal/conformance: make conformance test use specific go version

The use of internal/cmd/conformance/conformance.sh means that the test
is not hermetic since the script uses the system version of go,
rather than the specific versions chosen by integration_test.go.

Change the way the conformance test is invoked by turning it
into a Go unit test that integration_test.go can directly call.

Change-Id: I37d23341e1eda984f23f78757a38e862e5fac3d4
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/190518
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/integration_test.go b/integration_test.go
index ffe4b84..cbda497 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -82,18 +82,11 @@
 			runGo("ProtoLegacy", workDir, "go", "test", "-race", "-tags", "protolegacy", "./...")
 			runGo("ProtocGenGo", "cmd/protoc-gen-go/testdata", "go", "test")
 			runGo("ProtocGenGoGRPC", "cmd/protoc-gen-go-grpc/testdata", "go", "test")
+			runGo("Conformance", "internal/conformance", "go", "test", "-execute")
 		}
 	}
 	wg.Wait()
 
-	t.Run("ConformanceTests", func(t *testing.T) {
-		driverPath := filepath.Join("internal", "cmd", "conformance")
-		driver := filepath.Join(driverPath, "conformance.sh")
-		failureList := filepath.Join(driverPath, "failure_list.txt")
-		textFailureList := filepath.Join(driverPath, "text_failure_list.txt")
-		runner := filepath.Join(protobufPath, "conformance", "conformance-test-runner")
-		mustRunCommand(t, runner, "--failure_list", failureList, "--text_format_failure_list", textFailureList, "--enforce_recommended", driver)
-	})
 	t.Run("GeneratedGoFiles", func(t *testing.T) {
 		diff := mustRunCommand(t, "go", "run", "-tags", "protolegacy", "./internal/cmd/generate-types")
 		if strings.TrimSpace(diff) != "" {
diff --git a/internal/cmd/conformance/conformance.sh b/internal/cmd/conformance/conformance.sh
deleted file mode 100755
index c82ad6a..0000000
--- a/internal/cmd/conformance/conformance.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-# Copyright 2019 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-cd $(dirname $0)
-exec go run main.go $@
diff --git a/internal/cmd/conformance/main.go b/internal/conformance/conformance_test.go
similarity index 76%
rename from internal/cmd/conformance/main.go
rename to internal/conformance/conformance_test.go
index 74286c3..67c9793 100644
--- a/internal/cmd/conformance/main.go
+++ b/internal/conformance/conformance_test.go
@@ -2,15 +2,17 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This binary implements the conformance test subprocess protocol as documented
-// in conformance.proto.
-package main
+package conformance
 
 import (
 	"encoding/binary"
+	"flag"
 	"io"
 	"log"
 	"os"
+	"os/exec"
+	"path/filepath"
+	"testing"
 
 	"google.golang.org/protobuf/encoding/protojson"
 	"google.golang.org/protobuf/encoding/prototext"
@@ -19,6 +21,38 @@
 	pb "google.golang.org/protobuf/internal/testprotos/conformance"
 )
 
+func init() {
+	// When the environment variable RUN_AS_CONFORMANCE_PLUGIN is set,
+	// we skip running the tests and instead act as a conformance plugin.
+	// This allows the binary to pass itself to conformance.
+	if os.Getenv("RUN_AS_CONFORMANCE_PLUGIN") == "1" {
+		main()
+		os.Exit(0)
+	}
+}
+
+var (
+	execute   = flag.Bool("execute", false, "execute the conformance test")
+	protoRoot = flag.String("protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
+)
+
+func Test(t *testing.T) {
+	if !*execute {
+		t.SkipNow()
+	}
+	binPath := filepath.Join(*protoRoot, "conformance", "conformance-test-runner")
+	cmd := exec.Command(binPath,
+		"--failure_list", "failing_tests.txt",
+		"--text_format_failure_list", "failing_tests_text_format.txt",
+		"--enforce_recommended",
+		os.Args[0])
+	cmd.Env = append(os.Environ(), "RUN_AS_CONFORMANCE_PLUGIN=1")
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		t.Fatalf("execution error: %v\n\n%s", err, out)
+	}
+}
+
 func main() {
 	var sizeBuf [4]byte
 	inbuf := make([]byte, 0, 4096)
diff --git a/internal/cmd/conformance/failure_list.txt b/internal/conformance/failing_tests.txt
similarity index 100%
rename from internal/cmd/conformance/failure_list.txt
rename to internal/conformance/failing_tests.txt
diff --git a/internal/cmd/conformance/text_failure_list.txt b/internal/conformance/failing_tests_text_format.txt
similarity index 100%
rename from internal/cmd/conformance/text_failure_list.txt
rename to internal/conformance/failing_tests_text_format.txt