Allow FileTest to read from an abstracted line reader.

In GTest, we'll just burn the files into the binary and not worry about
this. Apparently test files is a one of computer science's great
unsolved problems and everyone has their own special-snowflake way of
doing it. Burning them into the executable is easier.

BUG=129

Change-Id: Ib39759ed4dba6eb9ba97f0282f000739ddf931fe
Reviewed-on: https://boringssl-review.googlesource.com/16506
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/test/file_test.cc b/crypto/test/file_test.cc
index 21ae3e4..87a6b96 100644
--- a/crypto/test/file_test.cc
+++ b/crypto/test/file_test.cc
@@ -15,11 +15,13 @@
 #include "file_test.h"
 
 #include <algorithm>
-#include <memory>
+#include <utility>
 
+#include <assert.h>
 #include <ctype.h>
 #include <errno.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -28,18 +30,10 @@
 #include "../internal.h"
 
 
-FileTest::FileTest(const char *path) {
-  file_ = fopen(path, "r");
-  if (file_ == nullptr) {
-    fprintf(stderr, "Could not open file %s: %s.\n", path, strerror(errno));
-  }
-}
+FileTest::FileTest(std::unique_ptr<FileTest::LineReader> reader)
+    : reader_(std::move(reader)) {}
 
-FileTest::~FileTest() {
-  if (file_ != nullptr) {
-    fclose(file_);
-  }
-}
+FileTest::~FileTest() {}
 
 // FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
 // if there is none.
@@ -104,23 +98,19 @@
 
   while (true) {
     // Read the next line.
-    if (fgets(buf.get(), kBufLen, file_) == nullptr) {
-      if (feof(file_)) {
+    switch (reader_->ReadLine(buf.get(), kBufLen)) {
+      case kReadError:
+        fprintf(stderr, "Error reading from input at line %u.\n", line_ + 1);
+        return kReadError;
+      case kReadEOF:
         // EOF is a valid terminator for a test.
         return start_line_ > 0 ? kReadSuccess : kReadEOF;
-      }
-      fprintf(stderr, "Error reading from input.\n");
-      return kReadError;
+      case kReadSuccess:
+        break;
     }
 
     line_++;
     size_t len = strlen(buf.get());
-    // Check for truncation.
-    if (len > 0 && buf[len - 1] != '\n' && !feof(file_)) {
-      fprintf(stderr, "Line %u too long.\n", line_);
-      return kReadError;
-    }
-
     if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == '\0') {
       // Empty lines delimit tests.
       if (start_line_ > 0) {
@@ -372,12 +362,51 @@
   instructions_[key] = value;
 }
 
+class FileLineReader : public FileTest::LineReader {
+ public:
+  explicit FileLineReader(const char *path) : file_(fopen(path, "r")) {}
+  ~FileLineReader() override {
+    if (file_ != nullptr) {
+      fclose(file_);
+    }
+  }
+
+  // is_open returns true if the file was successfully opened.
+  bool is_open() const { return file_ != nullptr; }
+
+  FileTest::ReadResult ReadLine(char *out, size_t len) override {
+    assert(len > 0);
+    if (file_ == nullptr) {
+      return FileTest::kReadError;
+    }
+
+    if (fgets(out, len, file_) == nullptr) {
+      return feof(file_) ? FileTest::kReadEOF : FileTest::kReadError;
+    }
+
+    if (strlen(out) == len - 1 && out[len - 2] != '\n' && !feof(file_)) {
+      fprintf(stderr, "Line too long.\n");
+      return FileTest::kReadError;
+    }
+
+    return FileTest::kReadSuccess;
+  }
+
+ private:
+  FILE *file_;
+
+  FileLineReader(const FileLineReader &) = delete;
+  FileLineReader &operator=(const FileLineReader &) = delete;
+};
+
 int FileTestMainSilent(FileTestFunc run_test, void *arg, const char *path) {
-  FileTest t(path);
-  if (!t.is_open()) {
+  std::unique_ptr<FileLineReader> reader(new FileLineReader(path));
+  if (!reader->is_open()) {
+    fprintf(stderr, "Could not open file %s: %s.\n", path, strerror(errno));
     return 1;
   }
 
+  FileTest t(std::move(reader));
   bool failed = false;
   while (true) {
     FileTest::ReadResult ret = t.ReadNext();
diff --git a/crypto/test/file_test.h b/crypto/test/file_test.h
index 1a52e26..2c03f90 100644
--- a/crypto/test/file_test.h
+++ b/crypto/test/file_test.h
@@ -18,12 +18,12 @@
 #include <openssl/base.h>
 
 #include <stdint.h>
-#include <stdio.h>
 
 OPENSSL_MSVC_PRAGMA(warning(push))
 OPENSSL_MSVC_PRAGMA(warning(disable : 4702))
 
 #include <map>
+#include <memory>
 #include <set>
 #include <string>
 #include <vector>
@@ -86,18 +86,21 @@
 
 class FileTest {
  public:
-  explicit FileTest(const char *path);
-  ~FileTest();
-
-  // is_open returns true if the file was successfully opened.
-  bool is_open() const { return file_ != nullptr; }
-
   enum ReadResult {
     kReadSuccess,
     kReadEOF,
     kReadError,
   };
 
+  class LineReader {
+   public:
+    virtual ~LineReader() {}
+    virtual ReadResult ReadLine(char *out, size_t len) = 0;
+  };
+
+  explicit FileTest(std::unique_ptr<LineReader> reader);
+  ~FileTest();
+
   // ReadNext reads the next test from the file. It returns |kReadSuccess| if
   // successfully reading a test and |kReadEOF| at the end of the file. On
   // error or if the previous test had unconsumed attributes, it returns
@@ -168,7 +171,7 @@
   void OnKeyUsed(const std::string &key);
   void OnInstructionUsed(const std::string &key);
 
-  FILE *file_ = nullptr;
+  std::unique_ptr<LineReader> reader_;
   // line_ is the number of lines read.
   unsigned line_ = 0;