Convert aes_test to GTest.

This introduces machinery to start embedding the test data files into
the crypto_test binary. Figuring out every CI's test data story is more
trouble than is worth it. The GTest FileTest runner is considerably
different from the old one:

- It returns void and expects failures to use the GTest EXPECT_* and
  ASSERT_* macros, rather than ExpectBytesEqual. This is more monkey
  work to convert, but ultimately less work to add new tests. I think
  it's also valuable for our FileTest and normal test patterns to align
  as much as possible. The line number is emitted via SCOPED_TRACE.

- I've intentionally omitted the Error attribute handling, since that
  doesn't work very well with the new callback. This means evp_test.cc
  will take a little more work to convert, but this is again to keep our
  two test patterns aligned.

- The callback takes a std::function rather than a C-style void pointer.
  This means we can go nuts with lambdas. It also places the path first
  so clang-format doesn't go nuts.

BUG=129

Change-Id: I0d1920a342b00e64043e3ea05f5f5af57bfe77b3
Reviewed-on: https://boringssl-review.googlesource.com/16507
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9abb82b..d5f9ed9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,6 +9,8 @@
   set(CMAKE_GENERATOR_CC cl)
 endif()
 
+include(sources.cmake)
+
 enable_language(C)
 enable_language(CXX)
 
@@ -265,6 +267,15 @@
 # themselves as dependencies next to the target definition.
 add_custom_target(all_tests)
 
+add_custom_command(
+  OUTPUT crypto_test_data.cc
+  COMMAND ${GO_EXECUTABLE} run util/embed_test_data.go ${CRYPTO_TEST_DATA} >
+  ${CMAKE_CURRENT_BINARY_DIR}/crypto_test_data.cc
+  DEPENDS util/embed_test_data.go ${CRYPTO_TEST_DATA}
+  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_library(crypto_test_data OBJECT crypto_test_data.cc)
+
 add_subdirectory(crypto)
 add_subdirectory(ssl)
 add_subdirectory(ssl/test)
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index ba6ae07..3a2e802 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -251,10 +251,13 @@
   dsa/dsa_test.cc
   err/err_test.cc
   evp/evp_extra_test.cc
+  fipsmodule/aes/aes_test.cc
   fipsmodule/ec/ec_test.cc
   fipsmodule/rand/ctrdrbg_test.cc
+  test/file_test_gtest.cc
   rsa_extra/rsa_test.cc
 
+  $<TARGET_OBJECTS:crypto_test_data>
   $<TARGET_OBJECTS:gtest_main>
   $<TARGET_OBJECTS:test_support>
 )
diff --git a/crypto/fipsmodule/CMakeLists.txt b/crypto/fipsmodule/CMakeLists.txt
index 4bcaf47..ab95fd6 100644
--- a/crypto/fipsmodule/CMakeLists.txt
+++ b/crypto/fipsmodule/CMakeLists.txt
@@ -199,16 +199,6 @@
 endif()
 
 add_executable(
-  aes_test
-
-  aes/aes_test.cc
-  $<TARGET_OBJECTS:test_support>
-)
-
-target_link_libraries(aes_test crypto)
-add_dependencies(all_tests aes_test)
-
-add_executable(
   bn_test
 
   bn/bn_test.cc
diff --git a/crypto/fipsmodule/aes/aes_test.cc b/crypto/fipsmodule/aes/aes_test.cc
index e1e9ca6..73ae255 100644
--- a/crypto/fipsmodule/aes/aes_test.cc
+++ b/crypto/fipsmodule/aes/aes_test.cc
@@ -18,77 +18,50 @@
 #include <memory>
 #include <vector>
 
+#include <gtest/gtest.h>
+
 #include <openssl/aes.h>
-#include <openssl/crypto.h>
 
 #include "../../internal.h"
 #include "../../test/file_test.h"
+#include "../../test/test_util.h"
 
 
-static bool TestRaw(FileTest *t) {
+static void TestRaw(FileTest *t) {
   std::vector<uint8_t> key, plaintext, ciphertext;
-  if (!t->GetBytes(&key, "Key") ||
-      !t->GetBytes(&plaintext, "Plaintext") ||
-      !t->GetBytes(&ciphertext, "Ciphertext")) {
-    return false;
-  }
+  ASSERT_TRUE(t->GetBytes(&key, "Key"));
+  ASSERT_TRUE(t->GetBytes(&plaintext, "Plaintext"));
+  ASSERT_TRUE(t->GetBytes(&ciphertext, "Ciphertext"));
 
-  if (plaintext.size() != AES_BLOCK_SIZE ||
-      ciphertext.size() != AES_BLOCK_SIZE) {
-    t->PrintLine("Plaintext or Ciphertext not a block size.");
-    return false;
-  }
+  ASSERT_EQ(static_cast<unsigned>(AES_BLOCK_SIZE), plaintext.size());
+  ASSERT_EQ(static_cast<unsigned>(AES_BLOCK_SIZE), ciphertext.size());
 
   AES_KEY aes_key;
-  if (AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key) != 0) {
-    t->PrintLine("AES_set_encrypt_key failed.");
-    return false;
-  }
+  ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key));
 
   // Test encryption.
   uint8_t block[AES_BLOCK_SIZE];
   AES_encrypt(plaintext.data(), block, &aes_key);
-  if (!t->ExpectBytesEqual(block, AES_BLOCK_SIZE, ciphertext.data(),
-                           ciphertext.size())) {
-    t->PrintLine("AES_encrypt gave the wrong output.");
-    return false;
-  }
+  EXPECT_EQ(Bytes(ciphertext), Bytes(block));
 
   // Test in-place encryption.
   OPENSSL_memcpy(block, plaintext.data(), AES_BLOCK_SIZE);
   AES_encrypt(block, block, &aes_key);
-  if (!t->ExpectBytesEqual(block, AES_BLOCK_SIZE, ciphertext.data(),
-                           ciphertext.size())) {
-    t->PrintLine("In-place AES_encrypt gave the wrong output.");
-    return false;
-  }
+  EXPECT_EQ(Bytes(ciphertext), Bytes(block));
 
-  if (AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key) != 0) {
-    t->PrintLine("AES_set_decrypt_key failed.");
-    return false;
-  }
+  ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key));
 
   // Test decryption.
   AES_decrypt(ciphertext.data(), block, &aes_key);
-  if (!t->ExpectBytesEqual(block, AES_BLOCK_SIZE, plaintext.data(),
-                           plaintext.size())) {
-    t->PrintLine("AES_decrypt gave the wrong output.");
-    return false;
-  }
+  EXPECT_EQ(Bytes(plaintext), Bytes(block));
 
   // Test in-place decryption.
   OPENSSL_memcpy(block, ciphertext.data(), AES_BLOCK_SIZE);
   AES_decrypt(block, block, &aes_key);
-  if (!t->ExpectBytesEqual(block, AES_BLOCK_SIZE, plaintext.data(),
-                           plaintext.size())) {
-    t->PrintLine("In-place AES_decrypt gave the wrong output.");
-    return false;
-  }
-
-  return true;
+  EXPECT_EQ(Bytes(plaintext), Bytes(block));
 }
 
-static bool TestKeyWrap(FileTest *t) {
+static void TestKeyWrap(FileTest *t) {
   // All test vectors use the default IV, so test both with implicit and
   // explicit IV.
   //
@@ -98,93 +71,61 @@
   };
 
   std::vector<uint8_t> key, plaintext, ciphertext;
-  if (!t->GetBytes(&key, "Key") ||
-      !t->GetBytes(&plaintext, "Plaintext") ||
-      !t->GetBytes(&ciphertext, "Ciphertext")) {
-    return false;
-  }
+  ASSERT_TRUE(t->GetBytes(&key, "Key"));
+  ASSERT_TRUE(t->GetBytes(&plaintext, "Plaintext"));
+  ASSERT_TRUE(t->GetBytes(&ciphertext, "Ciphertext"));
 
-  if (plaintext.size() + 8 != ciphertext.size()) {
-    t->PrintLine("Invalid Plaintext and Ciphertext lengths.");
-    return false;
-  }
+  ASSERT_EQ(plaintext.size() + 8, ciphertext.size())
+      << "Invalid Plaintext and Ciphertext lengths.";
 
+  // Test encryption.
   AES_KEY aes_key;
-  if (AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key) != 0) {
-    t->PrintLine("AES_set_encrypt_key failed.");
-    return false;
-  }
+  ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key));
 
+  // Test with implicit IV.
   std::unique_ptr<uint8_t[]> buf(new uint8_t[ciphertext.size()]);
-  if (AES_wrap_key(&aes_key, nullptr /* iv */, buf.get(), plaintext.data(),
-                   plaintext.size()) != static_cast<int>(ciphertext.size()) ||
-      !t->ExpectBytesEqual(buf.get(), ciphertext.size(), ciphertext.data(),
-                           ciphertext.size())) {
-    t->PrintLine("AES_wrap_key with implicit IV failed.");
-    return false;
-  }
+  int len = AES_wrap_key(&aes_key, nullptr /* iv */, buf.get(),
+                         plaintext.data(), plaintext.size());
+  ASSERT_GE(len, 0);
+  EXPECT_EQ(Bytes(ciphertext), Bytes(buf.get(), static_cast<size_t>(len)));
 
+  // Test with explicit IV.
   OPENSSL_memset(buf.get(), 0, ciphertext.size());
-  if (AES_wrap_key(&aes_key, kDefaultIV, buf.get(), plaintext.data(),
-                   plaintext.size()) != static_cast<int>(ciphertext.size()) ||
-      !t->ExpectBytesEqual(buf.get(), ciphertext.size(), ciphertext.data(),
-                           ciphertext.size())) {
-    t->PrintLine("AES_wrap_key with explicit IV failed.");
-    return false;
-  }
+  len = AES_wrap_key(&aes_key, kDefaultIV, buf.get(), plaintext.data(),
+                     plaintext.size());
+  ASSERT_GE(len, 0);
+  EXPECT_EQ(Bytes(ciphertext), Bytes(buf.get(), static_cast<size_t>(len)));
 
-  if (AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key) != 0) {
-    t->PrintLine("AES_set_decrypt_key failed.");
-    return false;
-  }
+  // Test decryption.
+  ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key));
 
+  // Test with implicit IV.
   buf.reset(new uint8_t[plaintext.size()]);
-  if (AES_unwrap_key(&aes_key, nullptr /* iv */, buf.get(), ciphertext.data(),
-                     ciphertext.size()) != static_cast<int>(plaintext.size()) ||
-      !t->ExpectBytesEqual(buf.get(), plaintext.size(), plaintext.data(),
-                           plaintext.size())) {
-    t->PrintLine("AES_unwrap_key with implicit IV failed.");
-    return false;
-  }
+  len = AES_unwrap_key(&aes_key, nullptr /* iv */, buf.get(), ciphertext.data(),
+                       ciphertext.size());
+  ASSERT_GE(len, 0);
+  EXPECT_EQ(Bytes(plaintext), Bytes(buf.get(), static_cast<size_t>(len)));
 
+  // Test with explicit IV.
   OPENSSL_memset(buf.get(), 0, plaintext.size());
-  if (AES_unwrap_key(&aes_key, kDefaultIV, buf.get(), ciphertext.data(),
-                     ciphertext.size()) != static_cast<int>(plaintext.size()) ||
-      !t->ExpectBytesEqual(buf.get(), plaintext.size(), plaintext.data(),
-                           plaintext.size())) {
-    t->PrintLine("AES_unwrap_key with explicit IV failed.");
-    return false;
-  }
+  len = AES_unwrap_key(&aes_key, kDefaultIV, buf.get(), ciphertext.data(),
+                       ciphertext.size());
+  ASSERT_GE(len, 0);
 
+  // Test corrupted ciphertext.
   ciphertext[0] ^= 1;
-  if (AES_unwrap_key(&aes_key, nullptr /* iv */, buf.get(), ciphertext.data(),
-                     ciphertext.size()) != -1) {
-    t->PrintLine("AES_unwrap_key with bad input unexpectedly succeeded.");
-    return false;
-  }
-
-  return true;
+  EXPECT_EQ(-1, AES_unwrap_key(&aes_key, nullptr /* iv */, buf.get(),
+                               ciphertext.data(), ciphertext.size()));
 }
 
-static bool TestAES(FileTest *t, void *arg) {
-  if (t->GetParameter() == "Raw") {
-    return TestRaw(t);
-  }
-  if (t->GetParameter() == "KeyWrap") {
-    return TestKeyWrap(t);
-  }
-
-  t->PrintLine("Unknown mode '%s'.", t->GetParameter().c_str());
-  return false;
-}
-
-int main(int argc, char **argv) {
-  CRYPTO_library_init();
-
-  if (argc != 2) {
-    fprintf(stderr, "%s <test file.txt>\n", argv[0]);
-    return 1;
-  }
-
-  return FileTestMain(TestAES, nullptr, argv[1]);
+TEST(AESTest, TestVectors) {
+  FileTestGTest("crypto/fipsmodule/aes/aes_tests.txt", [](FileTest *t) {
+    if (t->GetParameter() == "Raw") {
+      TestRaw(t);
+    } else if (t->GetParameter() == "KeyWrap") {
+      TestKeyWrap(t);
+    } else {
+      ADD_FAILURE() << "Unknown mode " << t->GetParameter();
+    }
+  });
 }
diff --git a/crypto/test/file_test.h b/crypto/test/file_test.h
index 2c03f90..70629f3 100644
--- a/crypto/test/file_test.h
+++ b/crypto/test/file_test.h
@@ -22,6 +22,7 @@
 OPENSSL_MSVC_PRAGMA(warning(push))
 OPENSSL_MSVC_PRAGMA(warning(disable : 4702))
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <set>
@@ -219,4 +220,8 @@
 // FAIL/PASS message to stdout.
 int FileTestMainSilent(FileTestFunc run_test, void *arg, const char *path);
 
+// FileTestGTest behaves like FileTestMain, but for GTest. |path| must be the
+// name of a test file embedded in the test binary.
+void FileTestGTest(const char *path, std::function<void(FileTest *)> run_test);
+
 #endif /* OPENSSL_HEADER_CRYPTO_TEST_FILE_TEST_H */
diff --git a/crypto/test/file_test_gtest.cc b/crypto/test/file_test_gtest.cc
new file mode 100644
index 0000000..90d12bf
--- /dev/null
+++ b/crypto/test/file_test_gtest.cc
@@ -0,0 +1,85 @@
+/* Copyright (c) 2017, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include "file_test.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+
+std::string GetTestData(const char *path);
+
+class StringLineReader : public FileTest::LineReader {
+ public:
+  explicit StringLineReader(const std::string &data)
+      : data_(data), offset_(0) {}
+
+  FileTest::ReadResult ReadLine(char *out, size_t len) override {
+    assert(len > 0);
+    if (offset_ == data_.size()) {
+      return FileTest::kReadEOF;
+    }
+
+    size_t idx = data_.find('\n', offset_);
+    if (idx == std::string::npos) {
+      idx = data_.size();
+    } else {
+      idx++;  // Include the newline.
+    }
+
+    if (idx - offset_ > len - 1) {
+      ADD_FAILURE() << "Line too long.";
+      return FileTest::kReadError;
+    }
+
+    memcpy(out, data_.data() + offset_, idx - offset_);
+    out[idx - offset_] = '\0';
+    offset_ = idx;
+    return FileTest::kReadSuccess;
+  }
+
+ private:
+  std::string data_;
+  size_t offset_;
+
+  StringLineReader(const StringLineReader &) = delete;
+  StringLineReader &operator=(const StringLineReader &) = delete;
+};
+
+void FileTestGTest(const char *path, std::function<void(FileTest *)> run_test) {
+  std::unique_ptr<StringLineReader> reader(
+      new StringLineReader(GetTestData(path)));
+  FileTest t(std::move(reader));
+
+  while (true) {
+    switch (t.ReadNext()) {
+      case FileTest::kReadError:
+        ADD_FAILURE() << "Error reading test.";
+        return;
+      case FileTest::kReadEOF:
+        return;
+      case FileTest::kReadSuccess:
+        break;
+    }
+
+    SCOPED_TRACE(testing::Message() << path << ", line " << t.start_line());
+    run_test(&t);
+  }
+}
diff --git a/crypto/test/test_util.h b/crypto/test/test_util.h
index e03bff4..3bf41ab 100644
--- a/crypto/test/test_util.h
+++ b/crypto/test/test_util.h
@@ -22,6 +22,7 @@
 
 #include <iosfwd>
 #include <string>
+#include <vector>
 
 #include "../internal.h"
 
@@ -42,6 +43,8 @@
       : data(reinterpret_cast<const uint8_t *>(str)), len(strlen(str)) {}
   explicit Bytes(const std::string &str)
       : data(reinterpret_cast<const uint8_t *>(str.data())), len(str.size()) {}
+  explicit Bytes(const std::vector<uint8_t> &vec)
+      : data(vec.data()), len(vec.size()) {}
 
   template <size_t N>
   explicit Bytes(const uint8_t (&array)[N]) : data(array), len(N) {}
diff --git a/sources.cmake b/sources.cmake
new file mode 100644
index 0000000..185e961
--- /dev/null
+++ b/sources.cmake
@@ -0,0 +1,10 @@
+# This file contains source lists that are also consumed by
+# generate_build_files.py.
+#
+# TODO(davidben): Move the other source lists into this file.
+
+set(
+  CRYPTO_TEST_DATA
+
+  crypto/fipsmodule/aes/aes_tests.txt
+)
diff --git a/util/all_tests.json b/util/all_tests.json
index 49cb428..f926d07 100644
--- a/util/all_tests.json
+++ b/util/all_tests.json
@@ -36,7 +36,6 @@
 	["crypto/ecdh/ecdh_test", "crypto/ecdh/ecdh_tests.txt"],
 	["crypto/evp/evp_test", "crypto/evp/evp_tests.txt"],
 	["crypto/evp/pbkdf_test"],
-	["crypto/fipsmodule/aes_test", "crypto/fipsmodule/aes/aes_tests.txt"],
 	["crypto/fipsmodule/bn_test", "crypto/fipsmodule/bn/bn_tests.txt"],
 	["crypto/fipsmodule/ctrdrbg_vector_test", "crypto/fipsmodule/rand/ctrdrbg_vectors.txt"],
 	["crypto/fipsmodule/ecdsa_sign_test", "crypto/fipsmodule/ecdsa/ecdsa_sign_tests.txt"],
diff --git a/util/embed_test_data.go b/util/embed_test_data.go
new file mode 100644
index 0000000..0819530
--- /dev/null
+++ b/util/embed_test_data.go
@@ -0,0 +1,140 @@
+// Copyright (c) 2017, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+// embed_test_data generates a C++ source file which exports a function,
+// GetTestData, which looks up the specified data files.
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"unicode"
+)
+
+func quote(in []byte) string {
+	var buf bytes.Buffer
+	buf.WriteByte('"')
+	for _, b := range in {
+		switch b {
+		case '\a':
+			buf.WriteString(`\a`)
+		case '\b':
+			buf.WriteString(`\b`)
+		case '\f':
+			buf.WriteString(`\f`)
+		case '\n':
+			buf.WriteString(`\n`)
+		case '\r':
+			buf.WriteString(`\r`)
+		case '\t':
+			buf.WriteString(`\t`)
+		case '\v':
+			buf.WriteString(`\v`)
+		case '"':
+			buf.WriteString(`\"`)
+		default:
+			if unicode.IsPrint(rune(b)) {
+				buf.WriteByte(b)
+			} else {
+				fmt.Fprintf(&buf, "\\x%02x", b)
+			}
+		}
+	}
+	buf.WriteByte('"')
+	return buf.String()
+}
+
+func main() {
+	fmt.Printf(`/* Copyright (c) 2017, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+/* This file is generated by:
+`)
+	fmt.Printf(" *   go run util/embed_test_data.go")
+	for _, arg := range os.Args[1:] {
+		fmt.Printf(" \\\n *       %s", arg)
+	}
+	fmt.Printf(" */\n")
+
+	fmt.Printf(`
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+
+
+`)
+
+	// MSVC limits the length of string constants, so we emit an array of
+	// them and concatenate at runtime. We could also use a single array
+	// literal, but this is less compact.
+	const chunkSize = 8192
+
+	for i, arg := range os.Args[1:] {
+		data, err := ioutil.ReadFile(arg)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Error reading %s: %s.\n", data, err)
+			os.Exit(1)
+		}
+		fmt.Printf("static const char *kData%d[] = {\n", i)
+		for i := 0; i < len(data); i += chunkSize {
+			chunk := chunkSize
+			if chunk > len(data)-i {
+				chunk = len(data) - i
+			}
+			fmt.Printf("    %s,\n", quote(data[i:i+chunk]))
+		}
+		fmt.Printf("};\n")
+		fmt.Printf("static const size_t kLen%d = %d;\n\n", i, len(data))
+	}
+
+	fmt.Printf(`static std::string AssembleString(const char **data, size_t len) {
+  std::string ret;
+  for (size_t i = 0; i < len; i += %d) {
+    size_t chunk = std::min(static_cast<size_t>(%d), len - i);
+    ret.append(data[i / %d], chunk);
+  }
+  return ret;
+}
+
+/* Silence -Wmissing-declarations. */
+std::string GetTestData(const char *path);
+
+std::string GetTestData(const char *path) {
+`, chunkSize, chunkSize, chunkSize)
+	for i, arg := range os.Args[1:] {
+		fmt.Printf("  if (strcmp(path, %s) == 0) {\n", quote([]byte(arg)))
+		fmt.Printf("    return AssembleString(kData%d, kLen%d);\n", i, i)
+		fmt.Printf("  }\n")
+	}
+	fmt.Printf(`  fprintf(stderr, "File not embedded: %%s.\n", path);
+  abort();
+}
+`)
+
+}
diff --git a/util/generate_build_files.py b/util/generate_build_files.py
index a10d275..0b1be0b 100644
--- a/util/generate_build_files.py
+++ b/util/generate_build_files.py
@@ -486,8 +486,8 @@
   return not is_dir or dent != 'runner'
 
 
-def NotGTestMain(path, dent, is_dir):
-  return dent != 'gtest_main.cc'
+def NotGTestSupport(path, dent, is_dir):
+  return 'gtest' not in dent
 
 
 def SSLHeaderFiles(path, dent, is_dir):
@@ -630,12 +630,42 @@
   return asmfiles
 
 
+def ExtractVariablesFromCMakeFile(cmakefile):
+  """Parses the contents of the CMakeLists.txt file passed as an argument and
+  returns a dictionary of exported source lists."""
+  variables = {}
+  in_set_command = False
+  set_command = []
+  with open(cmakefile) as f:
+    for line in f:
+      if '#' in line:
+        line = line[:line.index('#')]
+      line = line.strip()
+
+      if not in_set_command:
+        if line.startswith('set('):
+          in_set_command = True
+          set_command = []
+      elif line == ')':
+        in_set_command = False
+        if not set_command:
+          raise ValueError('Empty set command')
+        variables[set_command[0]] = set_command[1:]
+      else:
+        set_command.extend([c for c in line.split(' ') if c])
+
+  if in_set_command:
+    raise ValueError('Unfinished set command')
+  return variables
+
+
 def IsGTest(path):
   with open(path) as f:
     return "#include <gtest/gtest.h>" in f.read()
 
 
 def main(platforms):
+  cmake = ExtractVariablesFromCMakeFile(os.path.join('src', 'sources.cmake'))
   crypto_c_files = FindCFiles(os.path.join('src', 'crypto'), NoTestsNorFIPSFragments)
   fips_fragments = FindCFiles(os.path.join('src', 'crypto', 'fipsmodule'), OnlyFIPSFragments)
   ssl_source_files = FindCFiles(os.path.join('src', 'ssl'), NoTests)
@@ -650,13 +680,24 @@
   crypto_c_files.append('err_data.c')
 
   test_support_c_files = FindCFiles(os.path.join('src', 'crypto', 'test'),
-                                    NotGTestMain)
+                                    NotGTestSupport)
   test_support_h_files = (
       FindHeaderFiles(os.path.join('src', 'crypto', 'test'), AllFiles) +
       FindHeaderFiles(os.path.join('src', 'ssl', 'test'), NoTestRunnerFiles))
 
+  # Generate crypto_test_data.cc
+  with open('crypto_test_data.cc', 'w+') as out:
+    subprocess.check_call(
+        ['go', 'run', 'util/embed_test_data.go'] + cmake['CRYPTO_TEST_DATA'],
+        cwd='src',
+        stdout=out)
+
   test_c_files = []
-  crypto_test_files = ['src/crypto/test/gtest_main.cc']
+  crypto_test_files = [
+      'crypto_test_data.cc',
+      'src/crypto/test/file_test_gtest.cc',
+      'src/crypto/test/gtest_main.cc',
+  ]
   # TODO(davidben): Remove this loop once all tests are converted.
   for path in FindCFiles(os.path.join('src', 'crypto'), OnlyTests):
     if IsGTest(path):