standard_out.h: Add ScopedBufferedOuptut class.

This allows buffering all OutputString() outputs to a buffer
instead of sending this to the real stdout. This is useful
for tests that need to call high-level functions that do not
return an Err value but instead invoke OutputString() directly.

Use it here in the Commands.ApplyTypeFilter which polluted the
test's stderr output with Err() dumps to stderr. This will
also be useful in future CLs.

Change-Id: I0333fa15b72149fad7d3fb8349e6cf5231038e08
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23080
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Andrew Grieve <agrieve@google.com>
diff --git a/src/gn/commands_unittest.cc b/src/gn/commands_unittest.cc
index 8c7ce5f..6fbe9ab 100644
--- a/src/gn/commands_unittest.cc
+++ b/src/gn/commands_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/values.h"
 #include "gn/commands.h"
 #include "gn/label_pattern.h"
+#include "gn/standard_out.h"
 #include "gn/target.h"
 #include "gn/test_with_scope.h"
 #include "util/test/test.h"
@@ -65,6 +66,10 @@
     created_targets.push_back(std::move(target));
   }
 
+  // Prevent Err()  messages sent to stderr from polluting
+  // the tests' own output.
+  ScopedBufferedOutput buffered_output;
+
   for (const auto& test_case : cases) {
     std::vector<const Target*> targets_to_filter = all_targets;
 
diff --git a/src/gn/err.cc b/src/gn/err.cc
index ca61e7c..91a4ac6 100644
--- a/src/gn/err.cc
+++ b/src/gn/err.cc
@@ -204,7 +204,7 @@
 }
 
 bool Err::PrintToStdout() const {
-  FlushBufferedOutput();
+  FlushBufferedLogOutput();
   return InternalPrintToStdout(false, true);
 }
 
diff --git a/src/gn/gn_main.cc b/src/gn/gn_main.cc
index 894f8e7..75441a6 100644
--- a/src/gn/gn_main.cc
+++ b/src/gn/gn_main.cc
@@ -92,7 +92,7 @@
 
   if (retval != 0) {
     // Failed, print out log info we buffered.
-    FlushBufferedOutput();
+    FlushBufferedLogOutput();
   }
 
   exit(retval);  // Don't free memory, it can be really slow!
diff --git a/src/gn/standard_out.cc b/src/gn/standard_out.cc
index ecc5ab8..abd21c9 100644
--- a/src/gn/standard_out.cc
+++ b/src/gn/standard_out.cc
@@ -220,6 +220,8 @@
 // Collects buffered log output for quiet mode.
 class QuietModeBuffer {
  public:
+  using BufferedOutput = ScopedBufferedOutput::Item;
+
   void Append(std::string_view output,
               TextDecoration decoration,
               HtmlEscaping escaping) {
@@ -251,13 +253,13 @@
     output_buffer_.clear();
   }
 
- private:
-  struct BufferedOutput {
-    std::string output;
-    TextDecoration decoration;
-    HtmlEscaping escaping;
-  };
+  // Take a snapshot of the current buffers output items.
+  std::vector<BufferedOutput> GetItems() {
+    std::lock_guard<std::mutex> lock(lock_);
+    return output_buffer_;
+  }
 
+ private:
   std::mutex lock_;
 
   // Set when we're in quiet mode but then flush the output. This means that
@@ -272,6 +274,9 @@
 // Non-null while buffering standard output. Deliberately leaked on shutdown.
 QuietModeBuffer* quiet_mode_buffer = nullptr;
 
+// Non-null while ScopedBufferedOutput singleton exists.
+QuietModeBuffer* scoped_output_buffer = nullptr;
+
 }  // namespace
 
 bool IsColorEnabled() {
@@ -282,12 +287,20 @@
 void OutputString(std::string_view output,
                   TextDecoration dec,
                   HtmlEscaping escaping) {
+  if (scoped_output_buffer) {
+    scoped_output_buffer->Append(output, dec, escaping);
+    return;
+  }
   WriteOutputString(output, dec, escaping);
 }
 
 void OutputLogString(std::string_view output,
                      TextDecoration dec,
                      HtmlEscaping escaping) {
+  if (scoped_output_buffer) {
+    scoped_output_buffer->Append(output, dec, escaping);
+    return;
+  }
   if (quiet_mode_buffer) {
     quiet_mode_buffer->Append(output, dec, escaping);
     return;
@@ -303,12 +316,29 @@
   quiet_mode_buffer = new QuietModeBuffer();
 }
 
-void FlushBufferedOutput() {
+void FlushBufferedLogOutput() {
   if (quiet_mode_buffer) {
     quiet_mode_buffer->Flush();
   }
 }
 
+ScopedBufferedOutput::ScopedBufferedOutput() {
+  DCHECK(!scoped_output_buffer)
+      << "Only one ScopedBufferedOutput instance can exist";
+  scoped_output_buffer = new QuietModeBuffer();
+}
+
+ScopedBufferedOutput::~ScopedBufferedOutput() {
+  DCHECK(scoped_output_buffer);
+  delete scoped_output_buffer;
+  scoped_output_buffer = nullptr;
+}
+
+std::vector<ScopedBufferedOutput::Item> ScopedBufferedOutput::GetItems() {
+  DCHECK(scoped_output_buffer);
+  return scoped_output_buffer->GetItems();
+}
+
 void PrintSectionHelp(const std::string& line,
                       const std::string& topic,
                       const std::string& tag) {
diff --git a/src/gn/standard_out.h b/src/gn/standard_out.h
index 4355c23..9bf1637 100644
--- a/src/gn/standard_out.h
+++ b/src/gn/standard_out.h
@@ -6,6 +6,7 @@
 #define TOOLS_GN_STANDARD_OUT_H_
 
 #include <string>
+#include <vector>
 
 enum TextDecoration {
   DECORATION_NONE = 0,
@@ -42,7 +43,7 @@
 // BufferLogOutput() is not threadsafe and is expected to be called from early
 // process init before we've created any worker thread.
 void BufferLogOutput();
-void FlushBufferedOutput();
+void FlushBufferedLogOutput();
 
 // If printing markdown, this generates table-of-contents entries with
 // links to the actual help; otherwise, prints a one-line description.
@@ -76,4 +77,32 @@
 
 bool IsColorEnabled();
 
+// Helper class used to redirect OutputString() and OutputLogString() calls
+// to an internal buffer. Useful for tests.
+//
+// Only one instance per process of that class can be used though. Usage is
+//
+//  1) Create instance, this automatically sets up the redirection.
+//  2) Call GetItems() to get a snapshot of the currently buffered items.
+//  3) On destruction, buffered output is dropped.
+//
+struct ScopedBufferedOutput {
+  // Constructor sets up the redirection, only one instance per process can
+  // exist at a given time, since this manages global state.
+  ScopedBufferedOutput();
+
+  // Destructor drops all buffered output items.
+  ~ScopedBufferedOutput();
+
+  // The input of a single buffered OutputString() or OutputLogString() call.
+  struct Item {
+    std::string output;
+    TextDecoration decoration;
+    HtmlEscaping escaping;
+  };
+
+  // Retrieve a snapshot (copy) of buffered items so far.
+  std::vector<Item> GetItems();
+};
+
 #endif  // TOOLS_GN_STANDARD_OUT_H_