Result: Support composite results (#775)

This change adds the `+=` operator to `amber::Result`, allowing results to contain more than one error message.
This simplifies the accumulation of errors produced by concurrent testing.

Also make `Result::Error()` return `"<no error message given>"` if a `Result` is constructed with an empty string.

Added tests.
diff --git a/include/amber/result.h b/include/amber/result.h
index 6e916be..c85c281 100644
--- a/include/amber/result.h
+++ b/include/amber/result.h
@@ -24,23 +24,30 @@
 class Result {
  public:
   /// Creates a result which succeeded.
-  Result();
+  Result() = default;
+
   /// Creates a result which failed and will return |err|.
   explicit Result(const std::string& err);
-  Result(const Result&);
-  ~Result();
+  inline Result(const Result&) = default;
+  inline Result(Result&&) = default;
 
-  Result& operator=(const Result&);
+  inline Result& operator=(const Result&) = default;
+  inline Result& operator=(Result&&) = default;
+
+  /// Adds the errors from |res| to this Result.
+  Result& operator+=(const Result& res);
+
+  /// Adds the error |err| to this Result.
+  Result& operator+=(const std::string& err);
 
   /// Returns true if the result is a success.
-  bool IsSuccess() const { return succeeded_; }
+  bool IsSuccess() const { return errors_.size() == 0; }
 
   /// Returns the error string if |IsSuccess| is false.
-  const std::string& Error() const { return error_; }
+  std::string Error() const;
 
  private:
-  bool succeeded_;
-  std::string error_;
+  std::vector<std::string> errors_;
 };
 
 }  // namespace amber
diff --git a/src/result.cc b/src/result.cc
index 39b3a85..160070d 100644
--- a/src/result.cc
+++ b/src/result.cc
@@ -14,16 +14,44 @@
 
 #include "amber/result.h"
 
+#include <sstream>
+
 namespace amber {
 
-Result::Result() : succeeded_(true) {}
+Result::Result(const std::string& err) {
+  errors_.emplace_back(err);
+}
 
-Result::Result(const std::string& err) : succeeded_(false), error_(err) {}
+Result& Result::operator+=(const Result& res) {
+  errors_.insert(std::end(errors_), std::begin(res.errors_),
+                 std::end(res.errors_));
+  return *this;
+}
 
-Result::Result(const Result&) = default;
+Result& Result::operator+=(const std::string& err) {
+  errors_.emplace_back(err);
+  return *this;
+}
 
-Result::~Result() = default;
-
-Result& Result::operator=(const Result&) = default;
+std::string Result::Error() const {
+  static const char* kNoErrorMsg = "<no error message given>";
+  switch (errors_.size()) {
+    case 0:
+      return "";
+    case 1:
+      return errors_[0].size() > 0 ? errors_[0] : kNoErrorMsg;
+    default: {
+      std::stringstream ss;
+      ss << errors_.size() << " errors:";
+      for (size_t i = 0; i < errors_.size(); i++) {
+        auto& err = errors_[i];
+        ss << "\n";
+        ss << " (" << (i + 1) << ") ";
+        ss << (err.size() > 0 ? err : kNoErrorMsg);
+      }
+      return ss.str();
+    }
+  }
+}
 
 }  // namespace amber
diff --git a/src/result_test.cc b/src/result_test.cc
index 7f850f9..de74837 100644
--- a/src/result_test.cc
+++ b/src/result_test.cc
@@ -31,6 +31,12 @@
   EXPECT_EQ("Test Failed", r.Error());
 }
 
+TEST_F(ResultTest, ErrorWithEmptyString) {
+  Result r("");
+  EXPECT_FALSE(r.IsSuccess());
+  EXPECT_EQ("<no error message given>", r.Error());
+}
+
 TEST_F(ResultTest, Copy) {
   Result r("Testing");
   Result r2(r);
@@ -39,4 +45,71 @@
   EXPECT_EQ("Testing", r2.Error());
 }
 
+TEST_F(ResultTest, Append1String) {
+  Result r;
+  r += "Test Failed";
+  EXPECT_EQ("Test Failed", r.Error());
+}
+
+TEST_F(ResultTest, Append3Strings) {
+  Result r;
+  r += "Error one";
+  r += "Error two";
+  r += "Error three";
+  EXPECT_EQ(R"(3 errors:
+ (1) Error one
+ (2) Error two
+ (3) Error three)",
+            r.Error());
+}
+
+TEST_F(ResultTest, Append1SingleErrorResult) {
+  Result r;
+  r += Result("Test Failed");
+  EXPECT_EQ("Test Failed", r.Error());
+}
+
+TEST_F(ResultTest, Append3SingleErrorResults) {
+  Result r;
+  r += Result("Error one");
+  r += Result("Error two");
+  r += Result("Error three");
+  EXPECT_EQ(R"(3 errors:
+ (1) Error one
+ (2) Error two
+ (3) Error three)",
+            r.Error());
+}
+
+TEST_F(ResultTest, Append3MixedResults) {
+  Result r;
+  r += Result("Error one");
+  r += Result();  // success
+  r += Result("Error two");
+  EXPECT_EQ(R"(2 errors:
+ (1) Error one
+ (2) Error two)",
+            r.Error());
+}
+
+TEST_F(ResultTest, AppendMultipleErrorResults) {
+  Result r1;
+  r1 += Result("r1 error one");
+  r1 += Result("r1 error two");
+  r1 += Result("r1 error three");
+  r1 += Result("");
+  Result r2;
+  r2 += Result("r2 error one");
+  r2 += r1;
+  r2 += Result("r2 error two");
+  EXPECT_EQ(R"(6 errors:
+ (1) r2 error one
+ (2) r1 error one
+ (3) r1 error two
+ (4) r1 error three
+ (5) <no error message given>
+ (6) r2 error two)",
+            r2.Error());
+}
+
 }  // namespace amber