Add test to make sure go-tuf interop data doesn't change

This is a test that makes sure that the metadata produced by go-tuf
cannot change out from under it without this metadata from being
regenerated. It does this by turning the generator script into a
library, and then doing a diff between the snapshotted metadata
and the one we just generated.

Change-Id: I3449bc000f77be6495e3198a786693eef40e446c
diff --git a/client/interop_test.go b/client/interop_test.go
index e64ec7a..13f0d8c 100644
--- a/client/interop_test.go
+++ b/client/interop_test.go
@@ -13,12 +13,67 @@
 	"github.com/flynn/go-tuf/data"
 	"github.com/flynn/go-tuf/util"
 	. "gopkg.in/check.v1"
+
+	goTufGenerator "github.com/flynn/go-tuf/client/testdata/go-tuf/generator"
 )
 
 type InteropSuite struct{}
 
 var _ = Suite(&InteropSuite{})
 
+func (InteropSuite) TestGoClientIdentityConsistentSnapshotFalse(c *C) {
+	checkGoIdentity(c, false)
+}
+
+func (InteropSuite) TestGoClientIdentityConsistentSnapshotTrue(c *C) {
+	checkGoIdentity(c, true)
+}
+
+func checkGoIdentity(c *C, consistentSnapshot bool) {
+	cwd, err := os.Getwd()
+	c.Assert(err, IsNil)
+	testDataDir := filepath.Join(cwd, "testdata")
+
+	tempDir, err := ioutil.TempDir("", "")
+	c.Assert(err, IsNil)
+	defer os.RemoveAll(tempDir)
+
+	// Generate the metadata and compute hashes for all the files.
+	goTufGenerator.Generate(tempDir, filepath.Join(testDataDir, "keys.json"), consistentSnapshot)
+	hashes := computeHashes(c, tempDir)
+
+	snapshotDir := filepath.Join(testDataDir, "go-tuf", fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot))
+	snapshotHashes := computeHashes(c, snapshotDir)
+
+	c.Assert(hashes, DeepEquals, snapshotHashes, Commentf("metadata out of date, regenerate by running client/testdata/go-tuf/regenerate-metadata.sh"))
+}
+
+func computeHashes(c *C, dir string) map[string]string {
+	hashes := make(map[string]string)
+
+	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return nil
+		}
+
+		bytes, err := ioutil.ReadFile(path)
+		if err != nil {
+			return err
+		}
+
+		path, err = filepath.Rel(dir, path)
+		if err != nil {
+			return err
+		}
+		hashes[path] = string(bytes)
+
+		return nil
+	})
+	c.Assert(err, IsNil)
+
+	return hashes
+}
+
 func (InteropSuite) TestGoClientCompatibility(c *C) {
 	names := []string{
 		"go-tuf",
diff --git a/client/testdata/go-tuf/generate.go b/client/testdata/go-tuf/generate.go
index 35562a5..a30e69a 100644
--- a/client/testdata/go-tuf/generate.go
+++ b/client/testdata/go-tuf/generate.go
@@ -1,146 +1,19 @@
 package main
 
 import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
-	"os/exec"
 	"path/filepath"
-	"time"
 
-	tuf "github.com/flynn/go-tuf"
-	"github.com/flynn/go-tuf/sign"
+	"github.com/flynn/go-tuf/client/testdata/go-tuf/generator"
 )
 
-var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
-
-type persistedKeys struct {
-	Encrypted bool               `json:"encrypted"`
-	Data      []*sign.PrivateKey `json:"data"`
-}
-
-func assertNotNil(err error) {
-	if err != nil {
-		panic(fmt.Sprintf("assertion failed: %s", err))
-	}
-}
-
-func copyRepo(src string, dst string) {
-	cmd := exec.Command("cp", "-r", src, dst)
-	assertNotNil(cmd.Run())
-}
-
-func newRepo(dir string) *tuf.Repo {
-	repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
-	assertNotNil(err)
-
-	return repo
-}
-
-func commit(dir string, repo *tuf.Repo) {
-	assertNotNil(repo.SnapshotWithExpires(tuf.CompressionTypeNone, expirationDate))
-	assertNotNil(repo.TimestampWithExpires(expirationDate))
-	assertNotNil(repo.Commit())
-
-	// Remove the keys directory to make sure we don't accidentally use a key.
-	assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
-}
-
-func addKeys(repo *tuf.Repo, roleKeys map[string][]*sign.PrivateKey) {
-	for role, keys := range roleKeys {
-		for _, key := range keys {
-			assertNotNil(repo.AddPrivateKeyWithExpires(role, key, expirationDate))
-		}
-	}
-}
-
-func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
-	paths := []string{}
-	for file, data := range files {
-		path := filepath.Join(dir, "staged", "targets", file)
-		assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
-		assertNotNil(ioutil.WriteFile(path, data, 0644))
-		paths = append(paths, file)
-	}
-	assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
-}
-
-func revokeKeys(repo *tuf.Repo, role string, keys []*sign.PrivateKey) {
-	for _, key := range keys {
-		assertNotNil(repo.RevokeKeyWithExpires(role, key.PublicData().IDs()[0], expirationDate))
-	}
-}
-
-func generateRepos(dir string, consistentSnapshot bool) {
-	f, err := os.Open("../keys.json")
-	assertNotNil(err)
-
-	var roleKeys map[string][][]*sign.PrivateKey
-	assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
-
-	// Collect all the initial keys we'll use when creating repositories.
-	// We'll modify this to reflect rotated keys.
-	keys := map[string][]*sign.PrivateKey{
-		"root":      roleKeys["root"][0],
-		"targets":   roleKeys["targets"][0],
-		"snapshot":  roleKeys["snapshot"][0],
-		"timestamp": roleKeys["timestamp"][0],
-	}
-
-	// Create the initial repo.
-	dir0 := filepath.Join(dir, "0")
-	repo0 := newRepo(dir0)
-	repo0.Init(consistentSnapshot)
-	addKeys(repo0, keys)
-	addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
-	commit(dir0, repo0)
-
-	// Rotate all the keys to make sure that works.
-	oldDir := dir0
-	i := 1
-	for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
-		// Setup the repo.
-		stepName := fmt.Sprintf("%d", i)
-		d := filepath.Join(dir, stepName)
-		copyRepo(oldDir, d)
-		repo := newRepo(d)
-		addKeys(repo, keys)
-
-		// Actually rotate the keys
-		revokeKeys(repo, role, roleKeys[role][0])
-		addKeys(repo, map[string][]*sign.PrivateKey{
-			role: roleKeys[role][1],
-		})
-		keys[role] = roleKeys[role][1]
-
-		// Add a target to make sure that works, then commit.
-		addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
-		commit(d, repo)
-
-		i += 1
-		oldDir = d
-	}
-
-	// Add another target file to make sure the workflow worked.
-	stepName := fmt.Sprintf("%d", i)
-	d := filepath.Join(dir, stepName)
-	copyRepo(oldDir, d)
-	repo := newRepo(d)
-	addKeys(repo, keys)
-	addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
-	commit(d, repo)
-}
-
 func main() {
 	cwd, err := os.Getwd()
-	assertNotNil(err)
-
-	for _, consistentSnapshot := range []bool{false, true} {
-		name := fmt.Sprintf("consistent-snapshot-%t", consistentSnapshot)
-		log.Printf("generating %s", name)
-		generateRepos(filepath.Join(cwd, name), consistentSnapshot)
+	if err != nil {
+		log.Fatal(err)
 	}
 
+	generator.Generate(filepath.Join(cwd, "consistent-snapshot-false"), "../keys.json", false)
+	generator.Generate(filepath.Join(cwd, "consistent-snapshot-true"), "../keys.json", true)
 }
diff --git a/client/testdata/go-tuf/generator/generator.go b/client/testdata/go-tuf/generator/generator.go
new file mode 100644
index 0000000..68bcec3
--- /dev/null
+++ b/client/testdata/go-tuf/generator/generator.go
@@ -0,0 +1,140 @@
+package generator
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"time"
+
+	tuf "github.com/flynn/go-tuf"
+	"github.com/flynn/go-tuf/sign"
+)
+
+var expirationDate = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+type persistedKeys struct {
+	Encrypted bool               `json:"encrypted"`
+	Data      []*sign.PrivateKey `json:"data"`
+}
+
+func assertNotNil(err error) {
+	if err != nil {
+		panic(fmt.Sprintf("assertion failed: %s", err))
+	}
+}
+
+func copyRepo(src string, dst string) {
+	cmd := exec.Command("cp", "-r", src, dst)
+	assertNotNil(cmd.Run())
+}
+
+func newRepo(dir string) *tuf.Repo {
+	repo, err := tuf.NewRepoIndent(tuf.FileSystemStore(dir, nil), "", "\t")
+	assertNotNil(err)
+
+	return repo
+}
+
+func commit(dir string, repo *tuf.Repo) {
+	assertNotNil(repo.SnapshotWithExpires(tuf.CompressionTypeNone, expirationDate))
+	assertNotNil(repo.TimestampWithExpires(expirationDate))
+	assertNotNil(repo.Commit())
+
+	// Remove the keys directory to make sure we don't accidentally use a key.
+	assertNotNil(os.RemoveAll(filepath.Join(dir, "keys")))
+}
+
+func addKeys(repo *tuf.Repo, roleKeys map[string][]*sign.PrivateKey) {
+	for role, keys := range roleKeys {
+		for _, key := range keys {
+			assertNotNil(repo.AddPrivateKeyWithExpires(role, key, expirationDate))
+		}
+	}
+}
+
+func addTargets(repo *tuf.Repo, dir string, files map[string][]byte) {
+	paths := []string{}
+	for file, data := range files {
+		path := filepath.Join(dir, "staged", "targets", file)
+		assertNotNil(os.MkdirAll(filepath.Dir(path), 0755))
+		assertNotNil(ioutil.WriteFile(path, data, 0644))
+		paths = append(paths, file)
+	}
+	assertNotNil(repo.AddTargetsWithExpires(paths, nil, expirationDate))
+}
+
+func revokeKeys(repo *tuf.Repo, role string, keys []*sign.PrivateKey) {
+	for _, key := range keys {
+		assertNotNil(repo.RevokeKeyWithExpires(role, key.PublicData().IDs()[0], expirationDate))
+	}
+}
+
+func generateRepos(dir string, roleKeys map[string][][]*sign.PrivateKey, consistentSnapshot bool) {
+	// Collect all the initial keys we'll use when creating repositories.
+	// We'll modify this to reflect rotated keys.
+	keys := map[string][]*sign.PrivateKey{
+		"root":      roleKeys["root"][0],
+		"targets":   roleKeys["targets"][0],
+		"snapshot":  roleKeys["snapshot"][0],
+		"timestamp": roleKeys["timestamp"][0],
+	}
+
+	// Create the initial repo.
+	dir0 := filepath.Join(dir, "0")
+	repo0 := newRepo(dir0)
+	repo0.Init(consistentSnapshot)
+	addKeys(repo0, keys)
+	addTargets(repo0, dir0, map[string][]byte{"0": []byte("0")})
+	commit(dir0, repo0)
+
+	// Rotate all the keys to make sure that works.
+	oldDir := dir0
+	i := 1
+	for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
+		// Setup the repo.
+		stepName := fmt.Sprintf("%d", i)
+		d := filepath.Join(dir, stepName)
+		copyRepo(oldDir, d)
+		repo := newRepo(d)
+		addKeys(repo, keys)
+
+		// Actually rotate the keys
+		revokeKeys(repo, role, roleKeys[role][0])
+		addKeys(repo, map[string][]*sign.PrivateKey{
+			role: roleKeys[role][1],
+		})
+		keys[role] = roleKeys[role][1]
+
+		// Add a target to make sure that works, then commit.
+		addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+		commit(d, repo)
+
+		i += 1
+		oldDir = d
+	}
+
+	// Add another target file to make sure the workflow worked.
+	stepName := fmt.Sprintf("%d", i)
+	d := filepath.Join(dir, stepName)
+	copyRepo(oldDir, d)
+	repo := newRepo(d)
+	addKeys(repo, keys)
+	addTargets(repo, d, map[string][]byte{stepName: []byte(stepName)})
+	commit(d, repo)
+}
+
+func Generate(dir string, keysPath string, consistentSnapshot bool) {
+	f, err := os.Open(keysPath)
+	assertNotNil(err)
+
+	var roleKeys map[string][][]*sign.PrivateKey
+	assertNotNil(json.NewDecoder(f).Decode(&roleKeys))
+
+	log.Printf("generating %s", dir)
+
+	generateRepos(dir, roleKeys, consistentSnapshot)
+}
diff --git a/client/testdata/go-tuf/regenerate-metadata.sh b/client/testdata/go-tuf/regenerate-metadata.sh
new file mode 100755
index 0000000..ea286a0
--- /dev/null
+++ b/client/testdata/go-tuf/regenerate-metadata.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -eu
+
+cd `dirname $0`
+
+for d in consistent-snapshot-false consistent-snapshot-true; do
+	if [[ -e $d ]]; then
+		rm -r $d
+	fi
+done
+
+go run generate.go
+go run ../tools/linkify-metadata.go